Ver código fonte

Merge branch 'test' of Web/web-server into master

huangzhichao 2 semanas atrás
pai
commit
e325c7ab69

+ 12 - 0
.dockerignore

@@ -0,0 +1,12 @@
+node_modules
+npm-debug.log
+yarn-debug.log
+yarn-error.log
+.git
+.gitignore
+README.md
+.env.local
+.env.development
+.env.test
+*.log
+.DS_Store

+ 1 - 1
.eslintrc.js

@@ -15,7 +15,7 @@ module.exports = {
     node: true,
     jest: true,
   },
-  ignorePatterns: ['.eslintrc.js'],
+  ignorePatterns: ['.eslintrc.js', 'ecosystem.config.js'],
   rules: {
     '@typescript-eslint/interface-name-prefix': 'off',
     '@typescript-eslint/explicit-function-return-type': 'off',

+ 14 - 15
Dockerfile

@@ -1,29 +1,28 @@
-# 使用docker环境对代码进行编译
+# Build stage
 FROM registry.cn-hangzhou.aliyuncs.com/stuuudy/node:18-alpine as builder
 
-# 安装 pnpm
+# Install pnpm
 RUN npm install -g pnpm
 
-# 设置工作目录
+# Set working directory
 WORKDIR /app
 
-# 复制 package.json 和 lock 文件
-COPY package*.json ./
-COPY pnpm-lock.yaml ./
+# Copy package files
+COPY package.json pnpm-lock.yaml ./
 
-# 安装依赖
-RUN pnpm install
+# Install dependencies
+RUN pnpm install --frozen-lockfile
 
-# 复制源代码
+# Copy source code
 COPY . .
 
+# Build application
+RUN pnpm build
 
-RUN yarn build
-
-# 将编译完成后的代码打包成dokcer镜像
+# Production stage
 FROM registry.cn-hangzhou.aliyuncs.com/stuuudy/node:18-alpine
 
-# 安装 PM2
+# Install PM2
 RUN npm install -g pm2
 
 WORKDIR /app
@@ -33,9 +32,9 @@ COPY --from=builder /app/dist ./dist
 COPY --from=builder /app/package*.json ./
 COPY --from=builder /app/node_modules ./node_modules
 COPY --from=builder /app/.env ./.env
-
+COPY --from=builder /app/ecosystem.config.js ./
 # 暴露端口(根据您的应用需要修改)
 EXPOSE 3000
 
 # 启动应用
-CMD ["pm2-runtime", "dist/main.js"] 
+CMD ["pm2-runtime", "start", "ecosystem.config.js"] 

+ 14 - 0
ecosystem.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  apps: [
+    {
+      name: 'agent-server',
+      script: './dist/main.js',
+      env_production: {
+        NODE_ENV: 'production'
+      },
+      env_development: {
+        NODE_ENV: 'development'
+      }
+    }
+  ]
+}

+ 5 - 0
package.json

@@ -20,9 +20,14 @@
     "test:e2e": "jest --config ./test/jest-e2e.json"
   },
   "dependencies": {
+    "@nestjs/axios": "^4.0.0",
     "@nestjs/common": "^10.0.0",
     "@nestjs/core": "^10.0.0",
     "@nestjs/platform-express": "^10.0.0",
+    "@nestjs/swagger": "^11.1.3",
+    "axios": "^1.8.4",
+    "class-transformer": "^0.5.1",
+    "class-validator": "^0.14.1",
     "reflect-metadata": "^0.2.0",
     "rxjs": "^7.8.1",
     "tencentcloud-sdk-nodejs": "^4.1.2"

+ 179 - 15
pnpm-lock.yaml

@@ -8,15 +8,30 @@ importers:
 
   .:
     dependencies:
+      '@nestjs/axios':
+        specifier: ^4.0.0
+        version: 4.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.8.4)(rxjs@7.8.2)
       '@nestjs/common':
         specifier: ^10.0.0
-        version: 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/core':
         specifier: ^10.0.0
-        version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+        version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nestjs/platform-express':
         specifier: ^10.0.0
-        version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
+        version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
+      '@nestjs/swagger':
+        specifier: ^11.1.3
+        version: 11.1.3(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
+      axios:
+        specifier: ^1.8.4
+        version: 1.8.4
+      class-transformer:
+        specifier: ^0.5.1
+        version: 0.5.1
+      class-validator:
+        specifier: ^0.14.1
+        version: 0.14.1
       reflect-metadata:
         specifier: ^0.2.0
         version: 0.2.2
@@ -35,7 +50,7 @@ importers:
         version: 10.2.3(chokidar@3.6.0)(typescript@5.8.2)
       '@nestjs/testing':
         specifier: ^10.0.0
-        version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15))
+        version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)
       '@types/express':
         specifier: ^4.17.17
         version: 4.17.21
@@ -434,6 +449,16 @@ packages:
     resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
     engines: {node: '>=8'}
 
+  '@microsoft/tsdoc@0.15.1':
+    resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
+
+  '@nestjs/axios@4.0.0':
+    resolution: {integrity: sha512-1cB+Jyltu/uUPNQrpUimRHEQHrnQrpLzVj6dU3dgn6iDDDdahr10TgHFGTmw5VuJ9GzKZsCLDL78VSwJAs/9JQ==}
+    peerDependencies:
+      '@nestjs/common': ^10.0.0 || ^11.0.0
+      axios: ^1.3.1
+      rxjs: ^7.0.0
+
   '@nestjs/cli@10.4.9':
     resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==}
     engines: {node: '>= 16.14'}
@@ -477,6 +502,19 @@ packages:
       '@nestjs/websockets':
         optional: true
 
+  '@nestjs/mapped-types@2.1.0':
+    resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==}
+    peerDependencies:
+      '@nestjs/common': ^10.0.0 || ^11.0.0
+      class-transformer: ^0.4.0 || ^0.5.0
+      class-validator: ^0.13.0 || ^0.14.0
+      reflect-metadata: ^0.1.12 || ^0.2.0
+    peerDependenciesMeta:
+      class-transformer:
+        optional: true
+      class-validator:
+        optional: true
+
   '@nestjs/platform-express@10.4.15':
     resolution: {integrity: sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==}
     peerDependencies:
@@ -488,6 +526,23 @@ packages:
     peerDependencies:
       typescript: '>=4.8.2'
 
+  '@nestjs/swagger@11.1.3':
+    resolution: {integrity: sha512-vhbW/Xu05Diti/EwYQp3Ea7Hj2M++wiakCcxqUUDA2n7NvCZC8LKsrcGynw6/x/lugdXyklYS+s2FhdAfeAikg==}
+    peerDependencies:
+      '@fastify/static': ^8.0.0
+      '@nestjs/common': ^11.0.1
+      '@nestjs/core': ^11.0.1
+      class-transformer: '*'
+      class-validator: '*'
+      reflect-metadata: ^0.1.12 || ^0.2.0
+    peerDependenciesMeta:
+      '@fastify/static':
+        optional: true
+      class-transformer:
+        optional: true
+      class-validator:
+        optional: true
+
   '@nestjs/testing@10.4.15':
     resolution: {integrity: sha512-eGlWESkACMKti+iZk1hs6FUY/UqObmMaa8HAN9JLnaYkoLf1Jeh+EuHlGnfqo/Rq77oznNLIyaA3PFjrFDlNUg==}
     peerDependencies:
@@ -529,6 +584,9 @@ packages:
   '@rtsao/scc@1.1.0':
     resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
 
+  '@scarf/scarf@1.4.0':
+    resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
+
   '@sinclair/typebox@0.27.8':
     resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
 
@@ -643,6 +701,9 @@ packages:
   '@types/supertest@6.0.2':
     resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==}
 
+  '@types/validator@13.15.0':
+    resolution: {integrity: sha512-nh7nrWhLr6CBq9ldtw0wx+z9wKnnv/uTVLA9g/3/TcOYxbpOSZE+MhKPmWqU+K0NvThjhv12uD8MuqijB0WzEA==}
+
   '@types/yargs-parser@21.0.3':
     resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
 
@@ -905,6 +966,9 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
+  axios@1.8.4:
+    resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
+
   babel-jest@29.7.0:
     resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -1043,6 +1107,12 @@ packages:
   cjs-module-lexer@1.4.3:
     resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
 
+  class-transformer@0.5.1:
+    resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
+
+  class-validator@0.14.1:
+    resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==}
+
   cli-cursor@3.1.0:
     resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
     engines: {node: '>=8'}
@@ -1557,6 +1627,15 @@ packages:
   flatted@3.3.3:
     resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
 
+  follow-redirects@1.15.9:
+    resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+
   for-each@0.3.5:
     resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
     engines: {node: '>= 0.4'}
@@ -2158,6 +2237,9 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
+  libphonenumber-js@1.12.6:
+    resolution: {integrity: sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==}
+
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
@@ -2455,6 +2537,10 @@ packages:
   path-to-regexp@3.3.0:
     resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
 
+  path-to-regexp@8.2.0:
+    resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
+    engines: {node: '>=16'}
+
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
@@ -2514,6 +2600,9 @@ packages:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
 
+  proxy-from-env@1.1.0:
+    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -2837,6 +2926,9 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
+  swagger-ui-dist@5.21.0:
+    resolution: {integrity: sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==}
+
   symbol-observable@4.0.0:
     resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==}
     engines: {node: '>=0.10'}
@@ -3069,6 +3161,10 @@ packages:
     resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
     engines: {node: '>=10.12.0'}
 
+  validator@13.15.0:
+    resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==}
+    engines: {node: '>= 0.10'}
+
   vary@1.1.2:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
@@ -3660,6 +3756,14 @@ snapshots:
 
   '@lukeed/csprng@1.1.0': {}
 
+  '@microsoft/tsdoc@0.15.1': {}
+
+  '@nestjs/axios@4.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.8.4)(rxjs@7.8.2)':
+    dependencies:
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      axios: 1.8.4
+      rxjs: 7.8.2
+
   '@nestjs/cli@10.4.9':
     dependencies:
       '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
@@ -3686,17 +3790,20 @@ snapshots:
       - uglify-js
       - webpack-cli
 
-  '@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+  '@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
     dependencies:
       iterare: 1.2.1
       reflect-metadata: 0.2.2
       rxjs: 7.8.2
       tslib: 2.8.1
       uid: 2.0.2
+    optionalDependencies:
+      class-transformer: 0.5.1
+      class-validator: 0.14.1
 
-  '@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
+  '@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
     dependencies:
-      '@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
@@ -3706,14 +3813,22 @@ snapshots:
       tslib: 2.8.1
       uid: 2.0.2
     optionalDependencies:
-      '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
+      '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
     transitivePeerDependencies:
       - encoding
 
-  '@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)':
+  '@nestjs/mapped-types@2.1.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)':
+    dependencies:
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      reflect-metadata: 0.2.2
+    optionalDependencies:
+      class-transformer: 0.5.1
+      class-validator: 0.14.1
+
+  '@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)':
     dependencies:
-      '@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2)
-      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       body-parser: 1.20.3
       cors: 2.8.5
       express: 4.21.2
@@ -3744,13 +3859,28 @@ snapshots:
     transitivePeerDependencies:
       - chokidar
 
-  '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15))':
+  '@nestjs/swagger@11.1.3(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)':
+    dependencies:
+      '@microsoft/tsdoc': 0.15.1
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/mapped-types': 2.1.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
+      js-yaml: 4.1.0
+      lodash: 4.17.21
+      path-to-regexp: 8.2.0
+      reflect-metadata: 0.2.2
+      swagger-ui-dist: 5.21.0
+    optionalDependencies:
+      class-transformer: 0.5.1
+      class-validator: 0.14.1
+
+  '@nestjs/testing@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)':
     dependencies:
-      '@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2)
-      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+      '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
       tslib: 2.8.1
     optionalDependencies:
-      '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
+      '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)
 
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
@@ -3779,6 +3909,8 @@ snapshots:
 
   '@rtsao/scc@1.1.0': {}
 
+  '@scarf/scarf@1.4.0': {}
+
   '@sinclair/typebox@0.27.8': {}
 
   '@sinonjs/commons@3.0.1':
@@ -3919,6 +4051,8 @@ snapshots:
       '@types/methods': 1.1.4
       '@types/superagent': 8.1.9
 
+  '@types/validator@13.15.0': {}
+
   '@types/yargs-parser@21.0.3': {}
 
   '@types/yargs@17.0.33':
@@ -4251,6 +4385,14 @@ snapshots:
     dependencies:
       possible-typed-array-names: 1.1.0
 
+  axios@1.8.4:
+    dependencies:
+      follow-redirects: 1.15.9
+      form-data: 4.0.2
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+
   babel-jest@29.7.0(@babel/core@7.26.10):
     dependencies:
       '@babel/core': 7.26.10
@@ -4432,6 +4574,14 @@ snapshots:
 
   cjs-module-lexer@1.4.3: {}
 
+  class-transformer@0.5.1: {}
+
+  class-validator@0.14.1:
+    dependencies:
+      '@types/validator': 13.15.0
+      libphonenumber-js: 1.12.6
+      validator: 13.15.0
+
   cli-cursor@3.1.0:
     dependencies:
       restore-cursor: 3.1.0
@@ -5041,6 +5191,8 @@ snapshots:
 
   flatted@3.3.3: {}
 
+  follow-redirects@1.15.9: {}
+
   for-each@0.3.5:
     dependencies:
       is-callable: 1.2.7
@@ -5876,6 +6028,8 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  libphonenumber-js@1.12.6: {}
+
   lines-and-columns@1.2.4: {}
 
   loader-runner@4.3.0: {}
@@ -6146,6 +6300,8 @@ snapshots:
 
   path-to-regexp@3.3.0: {}
 
+  path-to-regexp@8.2.0: {}
+
   path-type@4.0.0: {}
 
   picocolors@1.1.1: {}
@@ -6190,6 +6346,8 @@ snapshots:
       forwarded: 0.2.0
       ipaddr.js: 1.9.1
 
+  proxy-from-env@1.1.0: {}
+
   punycode@2.3.1: {}
 
   pure-rand@6.1.0: {}
@@ -6569,6 +6727,10 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
+  swagger-ui-dist@5.21.0:
+    dependencies:
+      '@scarf/scarf': 1.4.0
+
   symbol-observable@4.0.0: {}
 
   synckit@0.10.2:
@@ -6804,6 +6966,8 @@ snapshots:
       '@types/istanbul-lib-coverage': 2.0.6
       convert-source-map: 2.0.0
 
+  validator@13.15.0: {}
+
   vary@1.1.2: {}
 
   walker@1.0.8:

+ 2 - 1
src/app.module.ts

@@ -1,9 +1,10 @@
 import { Module } from '@nestjs/common'
 
+import { AgentServerModule } from '@/module/agent-server/agent-server.module'
 import { SqlAgentModule } from '@/module/sql-agent/sql-agent.module'
 import { TencentCloudModule } from '@/module/tencent-cloud/tencent-cloud.module'
 
 @Module({
-  imports: [SqlAgentModule, TencentCloudModule]
+  imports: [SqlAgentModule, TencentCloudModule, AgentServerModule]
 })
 export class AppModule {}

+ 12 - 0
src/decorators/cors.decorator.ts

@@ -0,0 +1,12 @@
+import { applyDecorators, Header, SetMetadata } from '@nestjs/common'
+
+export function Cors() {
+  return applyDecorators(
+    Header('Access-Control-Allow-Origin', '*'),
+    Header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'),
+    Header('Access-Control-Allow-Headers', 'Content-Type, Authorization'),
+    Header('Access-Control-Allow-Credentials', 'true'),
+    Header('Access-Control-Max-Age', '86400'),
+    SetMetadata('cors', true)
+  )
+}

+ 88 - 0
src/dto/agent-server.ts

@@ -0,0 +1,88 @@
+import {
+  IsString,
+  IsNotEmpty,
+  ValidateIf,
+  IsArray,
+  IsNumber,
+  IsObject
+} from 'class-validator'
+
+export class GetDialogueHistoryDto {
+  @IsString()
+  @IsNotEmpty({ message: 'staffId不能为空' })
+  staffId: string
+
+  @IsString()
+  @ValidateIf((o) => !o.unionId)
+  @IsNotEmpty({ message: 'userName不能为空' })
+  userName: string
+
+  @IsString()
+  @ValidateIf((o) => !o.userName)
+  @IsNotEmpty({ message: 'unionId不能为空' })
+  unionId: string
+
+  @IsString()
+  @IsNotEmpty({ message: 'recentMinutes不能为空' })
+  recentMinutes: string
+}
+
+export class GetBasePromptDto {
+  @IsString()
+  @IsNotEmpty({ message: 'scene不能为空' })
+  scene: string
+}
+
+export class GetUserProfileDto {
+  @IsString()
+  @IsNotEmpty({ message: 'user_id不能为空' })
+  userId: string
+}
+
+export class GetStaffProfileDto {
+  @IsString()
+  @IsNotEmpty({ message: 'staff_id不能为空' })
+  staffId: string
+}
+
+export class RunPromptDto {
+  @IsString()
+  @IsNotEmpty({ message: 'scene不能为空' })
+  scene: string
+
+  @IsString()
+  @IsNotEmpty({ message: 'prompt不能为空' })
+  prompt: string
+
+  @IsObject()
+  @IsNotEmpty({ message: 'staffProfile不能为空' })
+  staffProfile: StaffProfile
+
+  @IsObject()
+  @IsNotEmpty({ message: 'userProfile不能为空' })
+  userProfile: UserProfile
+
+  @IsArray()
+  @IsNotEmpty({ message: 'dialogueHistory不能为空' })
+  dialogueHistory: DialogueHistory
+
+  @IsString()
+  @IsNotEmpty({ message: 'modelName不能为空' })
+  modelName: string
+
+  @IsNumber()
+  @IsNotEmpty({ message: 'currentTimestamp不能为空' })
+  currentTimestamp: number
+}
+
+export class ListUsersDto {
+  @IsString()
+  @ValidateIf((o) => !o.unionId)
+  @IsNotEmpty({ message: 'userName不能为空' })
+  userName: string
+
+  @IsString()
+  @ValidateIf((o) => !o.userName)
+  @IsNotEmpty({ message: 'unionId不能为空' })
+  unionId: string
+}

+ 15 - 0
src/interface/agent-server.interface.ts

@@ -0,0 +1,15 @@
+import { ServiceResponse } from '../response/response.interface'
+
+export interface IAgentServerService {
+  listStaffs(): Promise<ServiceResponse<StaffsType[]>>
+  getDialogueHistory(
+    staffId: string,
+    userName: string,
+    unionId: string,
+    recentMinutes: string
+  ): Promise<ServiceResponse<UserInfo[]>>
+  listModels(): Promise<ServiceResponse<Model[]>>
+  listScenes(): Promise<ServiceResponse<any[]>>
+  getBasePrompt(scene: string): Promise<ServiceResponse<string>>
+  // runPrompt(prompt: any): Promise<ServiceResponse<any>>
+}

+ 11 - 0
src/main.ts

@@ -1,3 +1,4 @@
+import { ValidationPipe } from '@nestjs/common'
 import { NestFactory } from '@nestjs/core'
 
 import { AppModule } from './app.module'
@@ -8,6 +9,16 @@ async function bootstrap() {
     origin: '*'
   })
   app.setGlobalPrefix('webserver')
+  app.useGlobalPipes(
+    new ValidationPipe({
+      whitelist: true, // 过滤掉请求参数中未定义的属性
+      transform: true, // 将请求参数转换为指定的类型
+      // forbidNonWhitelisted: true, // 如果请求参数中存在未定义的属性,则抛出异常
+      transformOptions: {
+        enableImplicitConversion: true // 启用隐式转换
+      }
+    })
+  )
   await app.listen(3000, '0.0.0.0')
 }
 bootstrap()

+ 79 - 0
src/module/agent-server/agent-server.controller.ts

@@ -0,0 +1,79 @@
+import { Controller, Get, Query, Post, Body, Header } from '@nestjs/common'
+import { ApiOperation, ApiTags } from '@nestjs/swagger'
+
+import {
+  GetDialogueHistoryDto,
+  GetBasePromptDto,
+  GetUserProfileDto,
+  GetStaffProfileDto,
+  RunPromptDto,
+  ListUsersDto
+} from '@/dto/agent-server'
+
+import { AgentServerService } from './service/agent-server.service'
+
+@ApiTags('agent-server')
+@Controller('agent-server')
+export class AgentServerController {
+  constructor(private readonly agentServerService: AgentServerService) {}
+
+  @Get('staffs')
+  @ApiOperation({ summary: '获取客服列表' })
+  async listStaffs() {
+    return this.agentServerService.listStaffs()
+  }
+
+  @Get('dialogue/history')
+  @ApiOperation({ summary: '获取对话历史' })
+  async getDialogueHistory(@Query() query: GetDialogueHistoryDto) {
+    return this.agentServerService.getDialogueHistory(
+      query.staffId,
+      query.userName,
+      query.unionId,
+      query.recentMinutes
+    )
+  }
+
+  @Get('scenes')
+  @ApiOperation({ summary: '获取场景列表' })
+  async listScenes() {
+    return this.agentServerService.listScenes()
+  }
+
+  @Get('models')
+  @ApiOperation({ summary: '获取模型列表' })
+  async listModels() {
+    return this.agentServerService.listModels()
+  }
+
+  @Get('user/profile')
+  @ApiOperation({ summary: '获取用户信息' })
+  async getUserProfile(@Query() query: GetUserProfileDto) {
+    return this.agentServerService.getUserProfile(query.userId)
+  }
+
+  @Get('staff/profile')
+  @ApiOperation({ summary: '获取客服信息' })
+  async getStaffProfile(@Query() query: GetStaffProfileDto) {
+    return this.agentServerService.getStaffProfile(query.staffId)
+  }
+
+  @Get('base-prompt')
+  @ApiOperation({ summary: '获取基础提示词' })
+  async getBasePrompt(@Query() query: GetBasePromptDto) {
+    return this.agentServerService.getBasePrompt(query.scene)
+  }
+
+  @Post('run-prompt')
+  @Header('Content-Type', 'application/json')
+  @ApiOperation({ summary: '运行提示词' })
+  async runPrompt(@Body() body: RunPromptDto) {
+    return this.agentServerService.runPrompt(body)
+  }
+
+  @Get('user/list')
+  @ApiOperation({ summary: '获取用户列表' })
+  async listUsers(@Query() query: ListUsersDto) {
+    return this.agentServerService.listUsers(query.userName, query.unionId)
+  }
+}

+ 18 - 0
src/module/agent-server/agent-server.module.ts

@@ -0,0 +1,18 @@
+import { HttpModule } from '@nestjs/axios'
+import { Module } from '@nestjs/common'
+
+import { AgentServerController } from './agent-server.controller'
+import { AgentServerHttpService } from './service/agent-server-http.service'
+import { AgentServerService } from './service/agent-server.service'
+
+@Module({
+  imports: [
+    HttpModule.register({
+      timeout: 600000,
+      maxRedirects: 3
+    })
+  ],
+  controllers: [AgentServerController],
+  providers: [AgentServerHttpService, AgentServerService]
+})
+export class AgentServerModule {}

+ 110 - 0
src/module/agent-server/service/agent-server-http.service.ts

@@ -0,0 +1,110 @@
+import { HttpService } from '@nestjs/axios'
+import { Injectable } from '@nestjs/common'
+import { lastValueFrom } from 'rxjs'
+
+import { ServiceResponse } from '@/response/response.interface'
+import { HttpStatusCode } from '@/response/status-code.enum'
+
+@Injectable()
+export class AgentServerHttpService {
+  private readonly baseUrl = 'http://192.168.206.189:8083/api'
+
+  constructor(private readonly httpService: HttpService) {}
+
+  private async makeRequest<T>(
+    endpoint: string,
+    params?: Record<string, any>
+  ): Promise<ServiceResponse<T>> {
+    try {
+      const url = `${this.baseUrl}/${endpoint}`
+      const responseObservable = await this.httpService.get<ServiceResponse<T>>(
+        url,
+        { params }
+      )
+      const { data } = await lastValueFrom(responseObservable)
+      return data
+    } catch (error) {
+      return {
+        code: HttpStatusCode.INTERNAL_SERVER_ERROR,
+        msg: error.message,
+        data: null as T
+      }
+    }
+  }
+
+  async makeRequestPost<T>(
+    endpoint: string,
+    params?: Record<string, any>
+  ): Promise<ServiceResponse<T>> {
+    try {
+      const url = `${this.baseUrl}/${endpoint}`
+      const responseObservable = await this.httpService.post<
+        ServiceResponse<T>
+      >(url, params, {
+        headers: {
+          'Content-Type': 'application/json'
+        }
+      })
+      const { data } = await lastValueFrom(responseObservable)
+      return data
+    } catch (error) {
+      return {
+        code: HttpStatusCode.INTERNAL_SERVER_ERROR,
+        msg: error.message,
+        data: null as T
+      }
+    }
+  }
+
+  async listStaffs(): Promise<ServiceResponse<StaffsType[]>> {
+    return this.makeRequest<StaffsType[]>('listStaffs')
+  }
+
+  async getStaffProfile(
+    staff_id: string
+  ): Promise<ServiceResponse<StaffProfile>> {
+    return this.makeRequest<StaffProfile>('getStaffProfile', { staff_id })
+  }
+
+  async getUserProfile(user_id: string): Promise<ServiceResponse<UserProfile>> {
+    return this.makeRequest<UserProfile>('getUserProfile', { user_id })
+  }
+
+  async listUsers(
+    user_name: string,
+    user_union_id: string
+  ): Promise<ServiceResponse<UserProfile[]>> {
+    return this.makeRequest<UserProfile[]>('listUsers', {
+      user_name,
+      user_union_id
+    })
+  }
+
+  async getDialogueHistory(
+    staff_id: string,
+    user_id: string,
+    recent_minutes: string
+  ): Promise<ServiceResponse<DialogueHistory[]>> {
+    return this.makeRequest<DialogueHistory[]>('getDialogueHistory', {
+      staff_id,
+      user_id,
+      recent_minutes
+    })
+  }
+
+  async listModels(): Promise<ServiceResponse<Model[]>> {
+    return this.makeRequest<Model[]>('listModels')
+  }
+
+  async listScenes(): Promise<ServiceResponse<any[]>> {
+    return this.makeRequest<any[]>('listScenes')
+  }
+
+  async getBasePrompt(scene: string): Promise<ServiceResponse<string>> {
+    return this.makeRequest<string>('getBasePrompt', { scene })
+  }
+
+  async runPrompt(params: any): Promise<ServiceResponse<any>> {
+    return this.makeRequestPost<any>('runPrompt', params)
+  }
+}

+ 221 - 0
src/module/agent-server/service/agent-server.service.ts

@@ -0,0 +1,221 @@
+import { Injectable } from '@nestjs/common'
+
+import { RunPromptDto } from '@/dto/agent-server'
+import { IAgentServerService } from '@/interface/agent-server.interface'
+import { ServiceResponse } from '@/response/response.interface'
+import { BusinessCode, HttpStatusCode } from '@/response/status-code.enum'
+
+import { AgentServerHttpService } from './agent-server-http.service'
+
+@Injectable()
+export class AgentServerService implements IAgentServerService {
+  constructor(private readonly httpService: AgentServerHttpService) {}
+
+  async listStaffs(): Promise<ServiceResponse<StaffsType[]>> {
+    const { code, data, msg } = await this.httpService.listStaffs()
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取客服列表失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取客服列表成功',
+      data: data
+    }
+  }
+
+  async listScenes(): Promise<ServiceResponse<Scene[]>> {
+    const { code, data, msg } = await this.httpService.listScenes()
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取场景列表失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取场景列表成功',
+      data: data
+    }
+  }
+
+  async listModels(): Promise<ServiceResponse<Model[]>> {
+    const { code, data, msg } = await this.httpService.listModels()
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取模型列表失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取模型列表成功',
+      data: data
+    }
+  }
+
+  async getBasePrompt(scene: string): Promise<ServiceResponse<string>> {
+    const { code, data, msg } = await this.httpService.getBasePrompt(scene)
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取基础提示词失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取基础提示词成功',
+      data: data
+    }
+  }
+
+  async getDialogueHistory(
+    staffId: string,
+    userName: string,
+    unionId: string,
+    recentMinutes: string
+  ): Promise<ServiceResponse<UserInfo[]>> {
+    const user = await this.httpServiceListUsers(userName, unionId)
+    if (user.length === 0) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: '用户不存在',
+        data: null
+      }
+    }
+
+    const dialogueHistory = await Promise.all(
+      user.map(async (user) => {
+        const res = await this.getDialogueHistoryByUserId(
+          staffId,
+          user,
+          recentMinutes
+        )
+        return res
+      })
+    )
+
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取对话历史成功',
+      data: dialogueHistory
+    }
+  }
+
+  async httpServiceListUsers(
+    userName: string,
+    unionId: string
+  ): Promise<UserProfile[]> {
+    const res = await this.httpService.listUsers(userName, unionId)
+    if (res.code !== HttpStatusCode.OK) {
+      return []
+    }
+    return res.data
+  }
+
+  async getDialogueHistoryByUserId(
+    staffId: string,
+    user: UserProfile,
+    recentMinutes: string
+  ): Promise<UserInfo> {
+    const res = await this.httpService.getDialogueHistory(
+      staffId,
+      user.third_party_user_id,
+      recentMinutes
+    )
+
+    const userInfo: UserInfo = {
+      ...user,
+      history: res.data || []
+    }
+
+    return userInfo
+  }
+
+  async getUserProfile(userId: string): Promise<ServiceResponse<UserProfile>> {
+    const { code, data, msg } = await this.httpService.getUserProfile(userId)
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取用户信息失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取用户信息成功',
+      data: data
+    }
+  }
+
+  async getStaffProfile(
+    staffId: string
+  ): Promise<ServiceResponse<StaffProfile>> {
+    const { code, data, msg } = await this.httpService.getStaffProfile(staffId)
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取客服信息失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取客服信息成功',
+      data: data
+    }
+  }
+
+  async runPrompt(query: RunPromptDto): Promise<ServiceResponse<string>> {
+    const { code, data, msg } = await this.httpService.runPrompt({
+      scene: query.scene,
+      prompt: query.prompt,
+      staff_profile: query.staffProfile,
+      user_profile: query.userProfile,
+      dialogue_history: query.dialogueHistory,
+      model_name: query.modelName,
+      current_timestamp: query.currentTimestamp
+    })
+    console.log(code, data, msg)
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '运行提示词失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '运行提示词成功',
+      data: data
+    }
+  }
+
+  async listUsers(
+    userName: string,
+    unionId: string
+  ): Promise<ServiceResponse<UserProfile[]>> {
+    const { code, data, msg } = await this.httpService.listUsers(
+      userName,
+      unionId
+    )
+    if (code !== HttpStatusCode.OK) {
+      return {
+        code: BusinessCode.BAD_REQUEST,
+        msg: msg || '获取用户列表失败',
+        data: null
+      }
+    }
+    return {
+      code: BusinessCode.SUCCESS,
+      msg: '获取用户列表成功',
+      data: data
+    }
+  }
+}

+ 51 - 0
src/module/agent-server/type.d.ts

@@ -0,0 +1,51 @@
+type AgentServerResponseType<T> = {
+  code: number
+  msg: string
+  data: T
+}
+
+type StaffsType = {
+  agent_name: string
+  name: string
+  third_party_user_id: string
+  wxid: string
+}
+
+type StaffProfile = {
+  staff_id: string
+  name: string
+}
+
+type UserProfile = {
+  gender: number
+  iconurl: string
+  name: string
+  third_party_user_id: string
+  wxid: string
+}
+
+type DialogueHistory = {
+  role: string
+  timestamp: number
+  content: string
+}
+
+type Model = {
+  model_id: string
+  model_name: string
+  display_name: string
+}
+
+type Scene = {
+  scene: string
+  display_name: string
+}
+
+type BasePrompt = {
+  model_name: string
+  content: string
+}
+
+interface UserInfo extends UserProfile {
+  history: DialogueHistory[]
+}

+ 10 - 0
src/response/message.constant.ts

@@ -0,0 +1,10 @@
+export const ResponseMessage = {
+  SUCCESS: '请求成功',
+  BAD_REQUEST: '请求参数错误',
+  UNAUTHORIZED: '未授权',
+  FORBIDDEN: '禁止访问',
+  NOT_FOUND: '资源不存在',
+  INTERNAL_SERVER_ERROR: '服务器内部错误',
+  TOO_MANY_REQUESTS: '请求过于频繁',
+  UNKNOWN_ERROR: '未知错误'
+}

+ 11 - 0
src/response/response.interface.ts

@@ -0,0 +1,11 @@
+export interface ApiResponse<T> {
+  code: number
+  msg: string
+  data: T | null
+}
+
+export interface ServiceResponse<T> {
+  code: number
+  msg: string
+  data: T
+}

+ 39 - 0
src/response/response.util.ts

@@ -0,0 +1,39 @@
+import { ResponseMessage } from './message.constant'
+import { ApiResponse, ServiceResponse } from './response.interface'
+import { HttpStatusCode, BusinessCode } from './status-code.enum'
+
+export class ResponseUtil {
+  private static readonly statusCodeMap: Record<number, number> = {
+    [HttpStatusCode.OK]: BusinessCode.SUCCESS,
+    [HttpStatusCode.BAD_REQUEST]: BusinessCode.BAD_REQUEST,
+    [HttpStatusCode.UNAUTHORIZED]: BusinessCode.UNAUTHORIZED,
+    [HttpStatusCode.FORBIDDEN]: BusinessCode.FORBIDDEN,
+    [HttpStatusCode.NOT_FOUND]: BusinessCode.NOT_FOUND,
+    [HttpStatusCode.INTERNAL_SERVER_ERROR]: BusinessCode.INTERNAL_SERVER_ERROR,
+    [HttpStatusCode.TOO_MANY_REQUESTS]: BusinessCode.TOO_MANY_REQUESTS
+  }
+
+  private static readonly defaultMessages: Record<number, string> = {
+    [HttpStatusCode.OK]: ResponseMessage.SUCCESS,
+    [HttpStatusCode.BAD_REQUEST]: ResponseMessage.BAD_REQUEST,
+    [HttpStatusCode.UNAUTHORIZED]: ResponseMessage.UNAUTHORIZED,
+    [HttpStatusCode.FORBIDDEN]: ResponseMessage.FORBIDDEN,
+    [HttpStatusCode.NOT_FOUND]: ResponseMessage.NOT_FOUND,
+    [HttpStatusCode.INTERNAL_SERVER_ERROR]:
+      ResponseMessage.INTERNAL_SERVER_ERROR,
+    [HttpStatusCode.TOO_MANY_REQUESTS]: ResponseMessage.TOO_MANY_REQUESTS
+  }
+
+  static handleResponse<T>(response: ServiceResponse<T>): ApiResponse<T> {
+    const { code, msg, data } = response
+    const businessCode = this.statusCodeMap[code] || BusinessCode.UNKNOWN_ERROR
+    const message =
+      msg || this.defaultMessages[code] || ResponseMessage.UNKNOWN_ERROR
+
+    return {
+      code: businessCode,
+      msg: message,
+      data: businessCode === BusinessCode.SUCCESS ? data : null
+    }
+  }
+}

+ 20 - 0
src/response/status-code.enum.ts

@@ -0,0 +1,20 @@
+export enum HttpStatusCode {
+  OK = 200,
+  BAD_REQUEST = 400,
+  UNAUTHORIZED = 401,
+  FORBIDDEN = 403,
+  NOT_FOUND = 404,
+  INTERNAL_SERVER_ERROR = 500,
+  TOO_MANY_REQUESTS = 429
+}
+
+export enum BusinessCode {
+  SUCCESS = 0,
+  BAD_REQUEST = 1,
+  UNAUTHORIZED = 2,
+  FORBIDDEN = 3,
+  NOT_FOUND = 4,
+  INTERNAL_SERVER_ERROR = 5,
+  TOO_MANY_REQUESTS = 6,
+  UNKNOWN_ERROR = 99
+}