diff --git a/docker-compose.yaml b/docker-compose.yaml index 212c66a..c80031f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,7 +4,7 @@ services: eoapi-remote-server: # build: 从当前路径构建镜像 build: . - image: eoapi-remote-server + image: eoapi/eoapi-remote-server:1.8.0 container_name: eoapi-remote-server restart: always env_file: diff --git a/ecosystem.config.js b/ecosystem.config.js index f6d4a57..bdb1f30 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,7 +1,7 @@ // https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/ module.exports = [ { - script: 'dist/main.js', + script: 'dist/src/main.js', name: 'eoapi-remote-server', exec_mode: 'cluster', instances: 2, diff --git a/package.json b/package.json index 4eef60d..105bfee 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "start": "cross-env NODE_ENV=development nest start", "start:dev": "rimraf dist && cross-env NODE_ENV=development nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "cross-env NODE_ENV=production node dist/main.js", + "start:prod": "cross-env NODE_ENV=production node dist/src/main.js", "migration:create": "npx typeorm-ts-node-commonjs migration:create ./src/migrations/create-table", - "migration:generate": "npx typeorm-ts-node-commonjs migration:generate ./src/migrations/update-table -d ./src/config/data-source.ts", + "migration:generate": "node ./scripts/migration-generate.js", "migration:run": "npm run build&&npx typeorm-ts-node-commonjs migration:run -d ./src/config/data-source.ts", "migration:revert": "npm run typeorm migration:revert", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", diff --git a/scripts/migration-generate.js b/scripts/migration-generate.js new file mode 100644 index 0000000..926112c --- /dev/null +++ b/scripts/migration-generate.js @@ -0,0 +1,22 @@ +const { exec } = require('child_process'); + +const runMigrationGenerate = async function () { + const npm_package_version = process.env.npm_package_version.replaceAll( + '.', + '_', + ); + console.log('npm_package_version', npm_package_version); + exec( + `npx typeorm-ts-node-commonjs migration:generate ./src/migrations/update-table_${npm_package_version} -d ./src/config/data-source.ts`, + (error, stdout, stderr) => { + if (!error) { + // 成功 + console.log('更新成功', error); + } else { + // 失败 + console.log('更新失败', error); + } + }, + ); +}; +runMigrationGenerate(); diff --git a/src/common/class/res.class.ts b/src/common/class/res.class.ts index 39ac2fd..6e03a9f 100644 --- a/src/common/class/res.class.ts +++ b/src/common/class/res.class.ts @@ -1,7 +1,10 @@ +import { version } from '../../../package.json'; + export class ResOp { readonly data: any; readonly statusCode: number; readonly message: string; + readonly version = version; constructor(code: number, data?: any, message = 'success') { this.statusCode = code; diff --git a/src/common/filters/api-exception.filter.ts b/src/common/filters/api-exception.filter.ts index 3e73d2d..b4d75fc 100644 --- a/src/common/filters/api-exception.filter.ts +++ b/src/common/filters/api-exception.filter.ts @@ -9,6 +9,11 @@ import { ApiException } from '../exceptions/api.exception'; import { ResOp } from '../class/res.class'; import { isDev } from '@/utils'; +const errorTips = { + 401: '未登录', + 403: '拒绝访问', +} as const; + /** * 异常接管,统一异常返回数据 */ diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 3215902..9d5b4f8 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -16,9 +16,9 @@ export const getConfiguration = () => ({ entities: [__dirname + '/../**/entities/*.entity.{ts,js}'], autoLoadEntities: true, synchronize: false, - logging: false, + logging: ['error'], timezone: '+08:00', // 东八区 - migrations: ['dist/migrations/**/*.js'], + migrations: ['dist/src/migrations/**/*.js'], migrationsRun: true, cli: { migrationsDir: 'src/migrations', diff --git a/src/entities/apiTestHistory.entity.ts b/src/entities/apiTestHistory.entity.ts index d843e09..e018767 100644 --- a/src/entities/apiTestHistory.entity.ts +++ b/src/entities/apiTestHistory.entity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, TableColumnOptions } from 'typeorm'; +import { Column, Entity } from 'typeorm'; import { FictitiousBase } from './base.entity'; @Entity({ name: 'api_test_history' }) @@ -6,15 +6,15 @@ export class ApiTestHistory extends FictitiousBase { @Column({ default: 0 }) projectID: number; - @Column() + @Column({ nullable: true }) apiDataID: number; - @Column({ type: 'simple-json' }) + @Column({ type: 'json' }) general: string; - @Column({ type: 'simple-json' }) + @Column({ type: 'json' }) request: string; - @Column({ type: 'simple-json' }) + @Column({ type: 'json' }) response: string; } diff --git a/src/entities/mock.entity.ts b/src/entities/mock.entity.ts index 5a9048d..361ad19 100644 --- a/src/entities/mock.entity.ts +++ b/src/entities/mock.entity.ts @@ -9,7 +9,7 @@ export class Mock extends Base { @Column() apiDataID: number; - @Column({ type: 'longtext' }) + @Column({ type: 'json' }) response: string; @Column() diff --git a/src/migrations/1652764517480-InitData.ts b/src/migrations/1652764517480-InitData.ts index 37ad4b4..3ad0e5a 100644 --- a/src/migrations/1652764517480-InitData.ts +++ b/src/migrations/1652764517480-InitData.ts @@ -1,4 +1,5 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; +import { sampleApiData } from '../modules/workspace/apiData/samples/sample.api.data'; export class InitData1652764517480 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { @@ -12,340 +13,8 @@ export class InitData1652764517480 implements MigrationInterface { [1, '默认组', 1], ); - const items = [ - { - uuid: 1, - uniqueID: 'f2c2a5c2-a41a-428c-88ac-62f7a563d572', - name: '获取城市今日天气', - projectID: 1, - uri: 'http://www.weather.com.cn/data/cityinfo/{cityCode}.html', - groupID: 0, - protocol: 'http', - method: 'GET', - requestBodyType: 'raw', - requestBodyJsonType: 'object', - requestBody: {}, - queryParams: [], - restParams: [ - { - name: 'cityCode', - required: true, - example: '101010100', - description: - '城市代码 : http://www.mca.gov.cn/article/sj/xzqh/2020/20201201.html', - enum: [ - { default: true, value: '110000', description: 'Beijing' }, - { default: false, value: '440000', description: 'Guangdong' }, - { default: false, value: '', description: '' }, - ], - }, - ], - requestHeaders: [], - responseHeaders: [], - responseBodyType: 'json', - responseBodyJsonType: 'object', - responseBody: [ - { - name: 'weatherinfo', - required: true, - example: '', - type: 'object', - description: '', - children: [ - { - name: 'city', - description: '', - type: 'string', - required: true, - example: '北京', - }, - { - name: 'cityid', - description: '', - type: 'string', - required: true, - example: '101010100', - }, - { - name: 'temp1', - description: '最低温度', - type: 'string', - required: true, - example: '18℃', - }, - { - name: 'temp2', - description: '当日最高温度', - type: 'string', - required: true, - example: '31℃', - }, - { - name: 'weather', - description: '', - type: 'string', - required: true, - example: '多云转阴', - }, - { - name: 'img1', - description: '', - type: 'string', - required: true, - example: 'n1.gif', - }, - { - name: 'img2', - description: '', - type: 'string', - required: true, - example: 'd2.gif', - }, - { - name: 'ptime', - description: '', - type: 'string', - required: true, - example: '18:00', - }, - ], - }, - ], - weight: 0, - }, - { - uuid: 2, - uniqueID: '9ed1190a-d057-4127-94ca-0f99a8890e72', - name: '新冠全国疫情', - projectID: 1, - uri: 'https://view.inews.qq.com/g2/getOnsInfo', - groupID: 0, - protocol: 'http', - method: 'GET', - requestBodyType: 'raw', - requestBodyJsonType: 'object', - requestBody: {}, - queryParams: [{ name: 'name', required: true, example: 'disease_h5' }], - restParams: [], - requestHeaders: [], - responseHeaders: [ - { - name: 'date', - required: true, - description: '', - example: 'Sat, 05 Feb 2022 04:30:44 GMT', - }, - { - name: 'content-type', - required: true, - description: '', - example: 'application/json', - }, - { - name: 'transfer-encoding', - required: true, - description: '', - example: 'chunked', - }, - { - name: 'connection', - required: true, - description: '', - example: 'close', - }, - { - name: 'server', - required: true, - description: '', - example: 'openresty', - }, - { - name: 'tracecode', - required: true, - description: '', - example: '8QMewH9c6JodvyHb5wE=', - }, - { - name: 'x-client-ip', - required: true, - description: '', - example: '120.26.198.150', - }, - { - name: 'x-server-ip', - required: true, - description: '', - example: '58.250.137.40', - }, - ], - responseBodyType: 'json', - responseBodyJsonType: 'object', - responseBody: [ - { - name: 'ret', - description: '', - type: 'number', - required: true, - example: '', - }, - { - name: 'data', - description: '实际参数是 string,为了展示文档展开显示', - type: 'object', - required: true, - example: - '{"lastUpdateTime":"2022-02-05 11:52:51","chinaTotal":{"confirm":139641,"heal":126827,"dead":5700,"nowConfirm":7114,"suspect":2,"nowSevere":6,"importedCase":12684,"noInfect":887,"showLocalConfirm":1,"showlocalinfeciton":1,"localConfirm":851,"noInfectH5":109,"localConfirmH5":850,"local_acc_confirm":106297},"chinaAdd":{"confirm":321,"heal":165,"dead":0,"nowConfirm":156,"suspect":-2,"nowSevere":0,"importedCase":18,"noInfect":60,"localConfirm":-67,"noInfectH5":0,"localConfirmH5":9},"isShowAdd":true,"showAddSwitch":{"all":true,"confirm":true,"suspect":true,"dead":true,"heal":true,"nowConfirm":true,"nowSevere":true,"importedCase":true,"noInfect":true,"localConfirm":true,"localinfeciton":true},"areaTree":[{"name":"中国","today":{"confirm":321,"isUpdated":true},"total":{"nowConfirm":7114,"confirm":139641,"dead":5700,"showRate":false,"heal":126827,"showHeal":true,"wzz":0,"provinceLocalConfirm":0}}]}', - enum: [{ default: false, value: '', description: '' }], - children: [ - { - name: 'areaTree', - required: true, - example: '', - type: 'array', - description: '', - children: [ - { - name: 'name', - description: '', - type: 'string', - required: true, - example: '中国', - }, - { - name: 'today', - required: true, - example: '', - type: 'object', - description: '', - children: [ - { - name: 'confirm', - description: '', - type: 'number', - required: true, - example: '321', - }, - { - name: 'isUpdated', - description: '', - type: 'boolean', - required: true, - example: 'true', - }, - ], - }, - { - name: 'total', - required: true, - example: '', - type: 'object', - description: '', - children: [ - { - name: 'nowConfirm', - description: '', - type: 'number', - required: true, - example: '7114', - }, - { - name: 'confirm', - description: '', - type: 'number', - required: true, - example: '139641', - }, - { - name: 'dead', - description: '', - type: 'number', - required: true, - example: '5700', - }, - { - name: 'showRate', - description: '', - type: 'boolean', - required: true, - example: '', - }, - { - name: 'heal', - description: '', - type: 'number', - required: true, - example: '126827', - }, - { - name: 'showHeal', - description: '', - type: 'boolean', - required: true, - example: 'true', - }, - { - name: 'wzz', - description: '', - type: 'number', - required: true, - example: '', - }, - { - name: 'provinceLocalConfirm', - description: '', - type: 'number', - required: true, - example: '', - }, - ], - }, - { - name: 'children', - type: 'array', - required: true, - example: '', - enum: [], - description: '', - }, - ], - }, - { - name: 'chinaTotal', - required: true, - example: '', - type: 'object', - description: '', - }, - { - name: 'chinaAdd', - required: true, - example: '', - type: 'object', - description: '', - }, - { - name: 'showAddSwitch', - required: true, - example: '', - type: 'object', - description: '', - }, - { - name: 'lastUpdateTime', - description: '', - type: 'object', - required: true, - example: '2022-02-05 11:52:51', - }, - ], - }, - ], - weight: 0, - }, - ]; - const apiData = []; - items.forEach((item) => { + sampleApiData.forEach((item) => { apiData.push([ item.uuid, item.uniqueID, diff --git a/src/migrations/1665476069355-update-table_1_8_0.ts b/src/migrations/1665476069355-update-table_1_8_0.ts new file mode 100644 index 0000000..a45278c --- /dev/null +++ b/src/migrations/1665476069355-update-table_1_8_0.ts @@ -0,0 +1,30 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class updateTable1801665476069355 implements MigrationInterface { + name = 'updateTable1801665476069355' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`api_test_history\` CHANGE \`apiDataID\` \`apiDataID\` int NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`general\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`general\` json NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`request\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`request\` json NOT NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`response\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`response\` json NOT NULL`); + await queryRunner.query(`ALTER TABLE \`mock\` DROP COLUMN \`response\``); + await queryRunner.query(`ALTER TABLE \`mock\` ADD \`response\` json NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`mock\` DROP COLUMN \`response\``); + await queryRunner.query(`ALTER TABLE \`mock\` ADD \`response\` longtext NOT NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`response\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`response\` text NOT NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`request\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`request\` text NOT NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` DROP COLUMN \`general\``); + await queryRunner.query(`ALTER TABLE \`api_test_history\` ADD \`general\` text NOT NULL`); + await queryRunner.query(`ALTER TABLE \`api_test_history\` CHANGE \`apiDataID\` \`apiDataID\` int NOT NULL`); + } + +} diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index bc15e6f..03a9214 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -49,7 +49,7 @@ export class AuthService implements OnModuleInit { ); if (!userEntity) { - throw new UnauthorizedException(); + throw new UnauthorizedException('用户不存在'); } return this.loginUser(userEntity); @@ -76,7 +76,7 @@ export class AuthService implements OnModuleInit { !authEntity || authEntity.refreshTokenExpiresAt < new Date().getTime() ) { - throw new UnauthorizedException(); + throw new UnauthorizedException('token已失效,请重新登录'); } return this.loginUser( { diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/src/modules/auth/guards/jwt-auth.guard.ts index 3beca20..1e7ba2a 100644 --- a/src/modules/auth/guards/jwt-auth.guard.ts +++ b/src/modules/auth/guards/jwt-auth.guard.ts @@ -37,7 +37,7 @@ export class JwtAuthGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const token = request.headers['authorization'] as string; if (isEmpty(token)) { - throw new UnauthorizedException(); + throw new UnauthorizedException('请先登录'); } try { // 挂载对象到当前请求上 @@ -48,14 +48,14 @@ export class JwtAuthGuard implements CanActivate { select: ['passwordVersion'], }); if (!isExit || passwordVersion !== request.currentUser.pv) { - throw new UnauthorizedException(); + throw new UnauthorizedException('您的密码已更新,请重新登录'); } } catch (e) { // 无法通过token校验 - throw new UnauthorizedException(); + throw new UnauthorizedException('token已失效,请重新登录'); } if (isEmpty(request.currentUser)) { - throw new UnauthorizedException(); + throw new UnauthorizedException('当前用户不存在'); } const workspaceID = Number(request?.params?.workspaceID); @@ -67,7 +67,7 @@ export class JwtAuthGuard implements CanActivate { }, }); if (!hasWorkspaceAuth) { - throw new ForbiddenException(); + throw new ForbiddenException('没有该空间访问权限'); } } diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index b1fd112..880726f 100644 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -19,7 +19,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { const user = await this.userRepository.findOne({ username }); if (!user) { - throw new UnauthorizedException(); + throw new UnauthorizedException('用户不存在'); } return user; } diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index cada79f..82b4ef5 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -117,7 +117,7 @@ export class UserService implements OnModuleInit { isFirstLogin: false, }; } else { - throw new ForbiddenException('密码错误'); + throw new ForbiddenException('账号或密码有误'); } } else { const other = await this.validateUser(userDto); diff --git a/src/modules/workspace/apiData/apiData.controller.ts b/src/modules/workspace/apiData/apiData.controller.ts index 898f50f..2a2e839 100644 --- a/src/modules/workspace/apiData/apiData.controller.ts +++ b/src/modules/workspace/apiData/apiData.controller.ts @@ -31,7 +31,7 @@ export class ApiDataController { constructor(private readonly service: ApiDataService) {} filterItem(item: any = {}, projectID) { this.JSON_FIELDS.forEach((field) => { - item[field] = item[field] ? JSON.stringify(item[field]) : '{}'; + item[field] = item[field] ? JSON.stringify(item[field]) : '[]'; item.projectID = projectID; }); return item; diff --git a/src/modules/workspace/apiData/apiData.service.ts b/src/modules/workspace/apiData/apiData.service.ts index 6aab7c8..36648f5 100644 --- a/src/modules/workspace/apiData/apiData.service.ts +++ b/src/modules/workspace/apiData/apiData.service.ts @@ -30,31 +30,23 @@ export class ApiDataService { return await this.repository.findByIds(ids); } async batchCreate(createDto: Array) { - return this.repository + const result = await this.repository .createQueryBuilder() .insert() .into(ApiData) .values(createDto) .execute(); + + const apiDatas = await this.repository.find({ where: result.identifiers }); + const mockList = apiDatas.map((item) => + this.mockService.createSystemMockDTO(item), + ); + this.mockService.batchCreate(mockList); + return result; } async findAll(query: Partial) { - const apiData = await this.repository.find({ where: query }); - const mockApiDataIds = ( - await this.mockRepository.find({ - where: { apiDataID: In(apiData.map((n) => n.uuid)) }, - }) - ).map((n) => n.apiDataID); - - const noDefaultMockApiDatas = apiData - .filter((n) => !mockApiDataIds.includes(n.uuid)) - .map((apiData) => this.mockService.createSystemMockDTO(apiData)); - - if (noDefaultMockApiDatas.length) { - await this.mockService.batchCreate(noDefaultMockApiDatas); - } - - return apiData; + return this.repository.find({ where: query }); } async findOne(options: FindOneOptions): Promise { diff --git a/src/modules/workspace/apiData/dto/create.dto.ts b/src/modules/workspace/apiData/dto/create.dto.ts index ffb99c8..f30e053 100644 --- a/src/modules/workspace/apiData/dto/create.dto.ts +++ b/src/modules/workspace/apiData/dto/create.dto.ts @@ -1,19 +1,19 @@ export class CreateDto { name: string; - description: string; + description?: string; projectID: number; groupID: number; uri: string; protocol: string; method: string; requestBodyType: string; - requestHeaders: string; + requestHeaders: any; requestBodyJsonType: string; - requestBody: string; - queryParams: string; - restParams: string; - responseHeaders: string; - responseBody: string; + requestBody: any; + queryParams: any; + restParams: any; + responseHeaders: any; + responseBody: any; responseBodyType: string; responseBodyJsonType: string; weight: number; diff --git a/src/modules/workspace/apiData/samples/sample.api.data.ts b/src/modules/workspace/apiData/samples/sample.api.data.ts new file mode 100644 index 0000000..c6b1d09 --- /dev/null +++ b/src/modules/workspace/apiData/samples/sample.api.data.ts @@ -0,0 +1,331 @@ +export const sampleApiData = [ + { + uuid: 1, + uniqueID: 'f2c2a5c2-a41a-428c-88ac-62f7a563d572', + name: '获取城市今日天气', + projectID: 1, + uri: 'http://www.weather.com.cn/data/cityinfo/{cityCode}.html', + groupID: 0, + protocol: 'http', + method: 'GET', + requestBodyType: 'raw', + requestBodyJsonType: 'object', + requestBody: {}, + queryParams: [], + restParams: [ + { + name: 'cityCode', + required: true, + example: '101010100', + description: + '城市代码 : http://www.mca.gov.cn/article/sj/xzqh/2020/20201201.html', + enum: [ + { default: true, value: '110000', description: 'Beijing' }, + { default: false, value: '440000', description: 'Guangdong' }, + { default: false, value: '', description: '' }, + ], + }, + ], + requestHeaders: [], + responseHeaders: [], + responseBodyType: 'json', + responseBodyJsonType: 'object', + responseBody: [ + { + name: 'weatherinfo', + required: true, + example: '', + type: 'object', + description: '', + children: [ + { + name: 'city', + description: '', + type: 'string', + required: true, + example: '北京', + }, + { + name: 'cityid', + description: '', + type: 'string', + required: true, + example: '101010100', + }, + { + name: 'temp1', + description: '最低温度', + type: 'string', + required: true, + example: '18℃', + }, + { + name: 'temp2', + description: '当日最高温度', + type: 'string', + required: true, + example: '31℃', + }, + { + name: 'weather', + description: '', + type: 'string', + required: true, + example: '多云转阴', + }, + { + name: 'img1', + description: '', + type: 'string', + required: true, + example: 'n1.gif', + }, + { + name: 'img2', + description: '', + type: 'string', + required: true, + example: 'd2.gif', + }, + { + name: 'ptime', + description: '', + type: 'string', + required: true, + example: '18:00', + }, + ], + }, + ], + weight: 0, + }, + { + uuid: 2, + uniqueID: '9ed1190a-d057-4127-94ca-0f99a8890e72', + name: '新冠全国疫情', + projectID: 1, + uri: 'https://view.inews.qq.com/g2/getOnsInfo', + groupID: 0, + protocol: 'http', + method: 'GET', + requestBodyType: 'raw', + requestBodyJsonType: 'object', + requestBody: {}, + queryParams: [{ name: 'name', required: true, example: 'disease_h5' }], + restParams: [], + requestHeaders: [], + responseHeaders: [ + { + name: 'date', + required: true, + description: '', + example: 'Sat, 05 Feb 2022 04:30:44 GMT', + }, + { + name: 'content-type', + required: true, + description: '', + example: 'application/json', + }, + { + name: 'transfer-encoding', + required: true, + description: '', + example: 'chunked', + }, + { + name: 'connection', + required: true, + description: '', + example: 'close', + }, + { + name: 'server', + required: true, + description: '', + example: 'openresty', + }, + { + name: 'tracecode', + required: true, + description: '', + example: '8QMewH9c6JodvyHb5wE=', + }, + { + name: 'x-client-ip', + required: true, + description: '', + example: '120.26.198.150', + }, + { + name: 'x-server-ip', + required: true, + description: '', + example: '58.250.137.40', + }, + ], + responseBodyType: 'json', + responseBodyJsonType: 'object', + responseBody: [ + { + name: 'ret', + description: '', + type: 'number', + required: true, + example: '', + }, + { + name: 'data', + description: '实际参数是 string,为了展示文档展开显示', + type: 'object', + required: true, + example: + '{"lastUpdateTime":"2022-02-05 11:52:51","chinaTotal":{"confirm":139641,"heal":126827,"dead":5700,"nowConfirm":7114,"suspect":2,"nowSevere":6,"importedCase":12684,"noInfect":887,"showLocalConfirm":1,"showlocalinfeciton":1,"localConfirm":851,"noInfectH5":109,"localConfirmH5":850,"local_acc_confirm":106297},"chinaAdd":{"confirm":321,"heal":165,"dead":0,"nowConfirm":156,"suspect":-2,"nowSevere":0,"importedCase":18,"noInfect":60,"localConfirm":-67,"noInfectH5":0,"localConfirmH5":9},"isShowAdd":true,"showAddSwitch":{"all":true,"confirm":true,"suspect":true,"dead":true,"heal":true,"nowConfirm":true,"nowSevere":true,"importedCase":true,"noInfect":true,"localConfirm":true,"localinfeciton":true},"areaTree":[{"name":"中国","today":{"confirm":321,"isUpdated":true},"total":{"nowConfirm":7114,"confirm":139641,"dead":5700,"showRate":false,"heal":126827,"showHeal":true,"wzz":0,"provinceLocalConfirm":0}}]}', + enum: [{ default: false, value: '', description: '' }], + children: [ + { + name: 'areaTree', + required: true, + example: '', + type: 'array', + description: '', + children: [ + { + name: 'name', + description: '', + type: 'string', + required: true, + example: '中国', + }, + { + name: 'today', + required: true, + example: '', + type: 'object', + description: '', + children: [ + { + name: 'confirm', + description: '', + type: 'number', + required: true, + example: '321', + }, + { + name: 'isUpdated', + description: '', + type: 'boolean', + required: true, + example: 'true', + }, + ], + }, + { + name: 'total', + required: true, + example: '', + type: 'object', + description: '', + children: [ + { + name: 'nowConfirm', + description: '', + type: 'number', + required: true, + example: '7114', + }, + { + name: 'confirm', + description: '', + type: 'number', + required: true, + example: '139641', + }, + { + name: 'dead', + description: '', + type: 'number', + required: true, + example: '5700', + }, + { + name: 'showRate', + description: '', + type: 'boolean', + required: true, + example: '', + }, + { + name: 'heal', + description: '', + type: 'number', + required: true, + example: '126827', + }, + { + name: 'showHeal', + description: '', + type: 'boolean', + required: true, + example: 'true', + }, + { + name: 'wzz', + description: '', + type: 'number', + required: true, + example: '', + }, + { + name: 'provinceLocalConfirm', + description: '', + type: 'number', + required: true, + example: '', + }, + ], + }, + { + name: 'children', + type: 'array', + required: true, + example: '', + enum: [], + description: '', + }, + ], + }, + { + name: 'chinaTotal', + required: true, + example: '', + type: 'object', + description: '', + }, + { + name: 'chinaAdd', + required: true, + example: '', + type: 'object', + description: '', + }, + { + name: 'showAddSwitch', + required: true, + example: '', + type: 'object', + description: '', + }, + { + name: 'lastUpdateTime', + description: '', + type: 'object', + required: true, + example: '2022-02-05 11:52:51', + }, + ], + }, + ], + weight: 0, + }, +]; diff --git a/src/modules/workspace/apiTestHistory/apiTestHistory.service.ts b/src/modules/workspace/apiTestHistory/apiTestHistory.service.ts index 649fef0..792d55c 100644 --- a/src/modules/workspace/apiTestHistory/apiTestHistory.service.ts +++ b/src/modules/workspace/apiTestHistory/apiTestHistory.service.ts @@ -14,6 +14,8 @@ export class ApiTestHistoryService { ) {} async create(createDto: CreateDto) { + createDto.general ??= '{}'; + createDto.apiDataID ??= -1; return await this.repository.save(createDto); } diff --git a/src/modules/workspace/mock/mock.controller.ts b/src/modules/workspace/mock/mock.controller.ts index 1ecd2b1..3960cad 100644 --- a/src/modules/workspace/mock/mock.controller.ts +++ b/src/modules/workspace/mock/mock.controller.ts @@ -19,6 +19,7 @@ import { CreateDto } from './dto/create.dto'; import { UpdateDto } from './dto/update.dto'; import { QueryDto } from './dto/query.dto'; import { WORKSPACE_PROJECT_PREFIX } from '@/common/contants/prefix.contants'; +import { Public } from '@/common/decorators/public.decorator'; @ApiTags('Mock') @Controller(`${WORKSPACE_PROJECT_PREFIX}/mock`) @@ -46,12 +47,14 @@ export class MockController { return this.service.findOne({ where: { uuid, projectID } }); } - @All(':projectID/**') + @All(':mockID/**') + @Public() async findMock( - @Param('projectID') projectID: string, + @Param('projectID', ParseIntPipe) projectID: number, + @Param('mockID', ParseIntPipe) mockID: number, @Req() request: Request, ) { - return this.service.findMock(projectID, request); + return this.service.findMock(projectID, mockID, request); } @Put(':uuid') diff --git a/src/modules/workspace/mock/mock.service.ts b/src/modules/workspace/mock/mock.service.ts index 8e07aa0..9e196aa 100644 --- a/src/modules/workspace/mock/mock.service.ts +++ b/src/modules/workspace/mock/mock.service.ts @@ -1,24 +1,29 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { ModuleRef } from '@nestjs/core'; import { Repository, FindOneOptions } from 'typeorm'; import { ApiData } from 'src/entities/apiData.entity'; import { Request } from 'express'; import { tree2obj } from 'src/utils'; -import { CreateDto as ApiDataCreateDto } from '../apiData/dto/create.dto'; import { CreateDto, CreateWay } from './dto/create.dto'; import { UpdateDto } from './dto/update.dto'; import { QueryDto } from './dto/query.dto'; import { Mock } from '@/entities/mock.entity'; +import { ApiDataService } from '@/modules/workspace/apiData/apiData.service'; @Injectable() export class MockService { + private apiDataService: ApiDataService; constructor( @InjectRepository(Mock) private readonly repository: Repository, - @InjectRepository(ApiData) - private readonly apiDataRepository: Repository, + private moduleRef: ModuleRef, ) {} + onModuleInit() { + this.apiDataService = this.moduleRef.get(ApiDataService, { strict: false }); + } + async create(createDto: CreateDto, createWay: CreateWay = 'custom') { createDto.createWay = createWay; return await this.repository.save(createDto); @@ -41,60 +46,68 @@ export class MockService { return await this.repository.findOne(options); } - async findMock(projectID: string, request: Request) { - const { path, method, query, body, protocol } = request; - const pathName = path.replace(`/mock/${projectID}`, ''); - const pathReg = new RegExp(`/?${pathName}/?`); - - if (Number.isNaN(+query.mockID)) { - const apiDataList = await this.apiDataRepository - .createQueryBuilder('api_data') - .where('api_data.projectID = :projectID', { - projectID: Number(projectID.replace('eo-', '')), - }) - // .andWhere('api_data.uri = :uri', { uri: pathName }) - .andWhere('api_data.method = :method', { - method: method.toLocaleUpperCase(), - }) - .andWhere('api_data.protocol = :protocol', { protocol }) - .getMany(); + isMatchUrl(apiData: ApiData, pathReg) { + if (Array.isArray(apiData.restParams) && apiData.restParams.length > 0) { + const restMap = apiData.restParams.reduce( + (p, c) => ((p[c.name] = c.example), p), + {}, + ); + const uri = apiData.uri.replace( + /\{(.+?)\}/g, + (match, p) => restMap[p] ?? match, + ); + console.log('restMap', restMap, apiData.uri, uri); + return pathReg.test(uri); + } + return false; + } - const apiData = apiDataList.find((n) => { - let uri = n.uri.trim(); - if (Array.isArray(n.restParams) && n.restParams.length > 0) { - const restMap = n.restParams.reduce( - (p, c) => ((p[c.name] = c.example), p), - {}, - ); - uri = uri.replace(/\{(.+?)\}/g, (match, p) => restMap[p] ?? match); - // console.log('restMap', restMap, n.uri, uri); - } - return n.method === method && pathReg.test(uri); - }); + async findMock(projectID: number, mockID: number, request: Request) { + const { path, method, params, body, protocol } = request; + // const pathName = path.replace(`/mock/${projectID}`, ''); + // const pathReg = new RegExp(`/?${pathName}/?`); - if (!apiData) { - return new NotFoundException( - '没有匹配到该mock,请检查请求方法或路径是否正确。', - ); - } - console.log('apiData', apiData); - if (apiData.responseBodyType === 'raw') { - return apiData.responseBody; - } else if (apiData.responseBodyType === 'json') { - return JSON.stringify( - tree2obj([].concat(apiData.responseBody), { - key: 'name', - valueKey: 'description', - }), - ); - } else { - return '{}'; + const mock = await this.repository.findOne({ + where: { uuid: mockID }, + }); + if (mock.createWay === 'custom') { + try { + return JSON.parse(mock.response); + } catch (error) { + return mock.response; } - } else { - const mock = await this.repository.findOne({ - where: { uuid: Number(query.mockID) }, - }); - return mock?.response || '{}'; + } + const pathReg = new RegExp(`^/?${params[0]}/?$`); + const apiData = await this.apiDataService.findOne({ + where: { + protocol, + uuid: mock.apiDataID, + method: method.toLocaleUpperCase(), + }, + }); + + if (!apiData || this.isMatchUrl(apiData, pathReg)) { + return new NotFoundException( + '没有匹配到该mock,请检查请求方法或路径是否正确。', + ); + } + + let result = ''; + + if (apiData.responseBodyType === 'raw') { + result = apiData.responseBody; + } else if (apiData.responseBodyType === 'json') { + result = JSON.stringify( + tree2obj([].concat(apiData.responseBody), { + key: 'name', + valueKey: 'description', + }), + ); + } + try { + return JSON.parse(result); + } catch (error) { + return result; } } @@ -112,7 +125,7 @@ export class MockService { description: '系统默认mock', apiDataID: apiData.uuid, projectID: apiData.projectID, - response: '', + response: apiData.responseBody || '[]', createWay: 'system', }; } diff --git a/src/modules/workspace/workspace.controller.ts b/src/modules/workspace/workspace.controller.ts index ea663ce..0993c46 100644 --- a/src/modules/workspace/workspace.controller.ts +++ b/src/modules/workspace/workspace.controller.ts @@ -31,6 +31,8 @@ import { IUser, User } from '@/common/decorators/user.decorator'; import { UserEntity } from '@/entities/user.entity'; import { Collections } from '@/modules/workspace/project/dto/import.dto'; import { ProjectService } from '@/modules/workspace/project/project.service'; +import { sampleApiData } from '@/modules/workspace/apiData/samples/sample.api.data'; +import { ApiDataService } from '@/modules/workspace/apiData/apiData.service'; @ApiBearerAuth() @ApiTags('workspace') @@ -39,6 +41,7 @@ export class WorkspaceController { constructor( private readonly workspaceService: WorkspaceService, private readonly projectService: ProjectService, + private readonly apiDataService: ApiDataService, ) {} @Post() @@ -48,7 +51,19 @@ export class WorkspaceController { @User() user: IUser, @Body() createDto: CreateWorkspaceDto, ): Promise { - return this.workspaceService.create(user.userId, createDto); + const workspace = await this.workspaceService.create( + user.userId, + createDto, + ); + const project = workspace.projects.at(0); + this.apiDataService.batchCreate( + sampleApiData.map((item) => { + Reflect.deleteProperty(item, 'uuid'); + Reflect.deleteProperty(item, 'uniqueID'); + return { ...item, projectID: project.uuid }; + }), + ); + return workspace; } @Post('upload')