{"id":26062467,"url":"https://github.com/aiden-fe/compass-service","last_synced_at":"2025-04-11T11:10:40.264Z","repository":{"id":40434649,"uuid":"484681318","full_name":"Aiden-FE/compass-service","owner":"Aiden-FE","description":"Nestjs backend service.","archived":false,"fork":false,"pushed_at":"2023-05-11T06:49:47.000Z","size":443,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T07:36:09.784Z","etag":null,"topics":["alicloud-sms","express","nestjs","nodejs","nodemailer","passport","passport-jwt","prisma","prisma-client","template","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aiden-FE.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-23T07:44:55.000Z","updated_at":"2024-07-02T01:34:53.000Z","dependencies_parsed_at":"2025-03-08T21:15:24.793Z","dependency_job_id":null,"html_url":"https://github.com/Aiden-FE/compass-service","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aiden-FE%2Fcompass-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aiden-FE%2Fcompass-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aiden-FE%2Fcompass-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aiden-FE%2Fcompass-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aiden-FE","download_url":"https://codeload.github.com/Aiden-FE/compass-service/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248381791,"owners_count":21094528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alicloud-sms","express","nestjs","nodejs","nodemailer","passport","passport-jwt","prisma","prisma-client","template","typescript"],"created_at":"2025-03-08T16:00:27.399Z","updated_at":"2025-04-11T11:10:40.242Z","avatar_url":"https://github.com/Aiden-FE.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- TOC --\u003e\n* [compass-service 2.0](#compass-service-20)\n  * [项目启动](#项目启动)\n  * [特性](#特性)\n    * [Monorepo 结构,易于扩展服务及公共资源沉淀](#monorepo-结构易于扩展服务及公共资源沉淀)\n    * [Typescript/Jest/Airbnb Eslint/Prettier](#typescriptjestairbnb-eslintprettier)\n    * [支持环境变量控制](#支持环境变量控制)\n    * [支持Google reCAPTCHA v3 人机校验](#支持google-recaptcha-v3-人机校验)\n    * [接口多版本支持](#接口多版本支持)\n    * [接口限流保护](#接口限流保护)\n    * [约束接口进参,移除非白名单属性,自动转换数据为符合预期的类型](#约束接口进参移除非白名单属性自动转换数据为符合预期的类型)\n    * [\u003cspan id=\"prisma\"\u003ePrismaORM 数据库管理\u003c/span\u003e](#span-idprisma-prismaorm-数据库管理-span)\n      * [如果你是新数据库](#如果你是新数据库)\n      * [如果你是现有数据库架构](#如果你是现有数据库架构)\n      * [维护数据模型](#维护数据模型)\n      * [web浏览数据库数据](#web浏览数据库数据)\n      * [迁移管理](#迁移管理)\n      * [隐私数据保护](#隐私数据保护)\n    * [统一的响应拦截器,规范返回数据](#统一的响应拦截器规范返回数据)\n    * [支持JWT校验 + 用户权限集验证](#支持jwt校验--用户权限集验证)\n    * [EMail邮件服务支持](#email邮件服务支持)\n    * [支持连接redis服务](#支持连接redis服务)\n    * [添加日志中间件,监控入站请求](#添加日志中间件监控入站请求)\n    * [支持Swagger API文档](#支持swagger-api文档)\n    * [helmet 安全的响应头设置](#helmet-安全的响应头设置)\n    * [默认启用 express json,urlencoded 中间件](#默认启用-express-jsonurlencoded-中间件)\n    * [集成circleciCI自动集成部署](#集成circlecici自动集成部署)\n  * [业务功能](#业务功能)\n    * [支持基于Chat gpt的AI对话功能](#支持基于chat-gpt的ai对话功能)\n    * [支持授权管理](#支持授权管理)\n    * [支持待办事项管理](#支持待办事项管理)\n    * [支持权限管理(未完善)](#支持权限管理--未完善-)\n    * [支持角色管理(未完善)](#支持角色管理--未完善-)\n    * [支持用户管理(未完善)](#支持用户管理--未完善-)\n  * [2.0 移除的特性](#20-移除的特性)\n  * [更多文档信息](#更多文档信息)\n\u003c!-- TOC --\u003e\n\n# compass-service 2.0\n\n本项目会以具体业务为实例不断完善内容,如果你希望使用本项目作为基础模板快速搭建项目,可使用[Compass Template](https://github.com/Aiden-FE/compass-template)内的Nest模板\n\n## 项目启动\n\u003e 首次启动请先参考下方 [Prisma ORM 管理](#prisma) 部分同步数据库架构\n\u003e \n\u003e 如果您不具备可用的mysql,redis或是可选的postgres环境,可以参考[部署基础环境](./deploy/README.md#部署基础环境)一键启动基础环境\n\n`npm install` 安装依赖\n\n根目录新建 .env 文件, 复制 .env.example 内容到 .env 文件,并按需调整配置内容.\n\n`npm run start:dev` 开发模式启动\n\n`npm run start:prod` 生产模式启动\n\n`npm run format` 执行代码格式化\n\n`npm run lint` 执行代码检查\n\n根据自身业务实际情况,修改项目内 \"FIXME: \" 标记部分的逻辑\n\n`npm run build` 构建项目\n\n## 特性\n\n### Monorepo 结构,易于扩展服务及公共资源沉淀\n\n`nest g app [project_name]` 创建一个子应用到monorepo\n\n`nest g lib [library_name]` 创建一个包到monorepo\n\n`./shared` 文件夹内直接添加可被共享的资源文件\n\n### Typescript/Jest/Airbnb Eslint/Prettier\n\n* 支持Typescript环境\n* `npm run format` 进行代码格式化\n* `npm run lint` 进行代码检查,默认基于Airbnb规范\n* `npm run test` 进行单元测试\n* `npm run test:e2e` 进行端到端测试\n\n### 支持环境变量控制\n\n支持.env文件控制环境变量,示例可见: .env.example,复制示例文件进入.env文件后按需配置即可\n\n### 支持Google reCAPTCHA v3 人机校验\n\n客户端:\n\n```html\n\u003c!-- 插入recaptcha脚本并指定key,如果是国内host需要替换为www.recaptcha.net --\u003e\n\u003cscript src=\"https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key\"\u003e\u003c/script\u003e\n\n\u003cscript\u003e\n  // 当点击某个提交按钮时进行人机静默校验,否则应该进行双重认证或拒绝认证\n  function onClick(e) {\n    e.preventDefault();\n    // 提示: reCAPTCHA_site_key为您在Google ReCaptcha注册的网站key\n    grecaptcha.ready(function() {\n      // action各种含义参考: https://developers.google.com/recaptcha/docs/v3?hl=zh-cn#interpreting_the_score\n      grecaptcha.execute('reCAPTCHA_site_key', {action: 'login'}).then(function(token) {\n        // 在此处添加您的逻辑,把表单数据跟token一起提供给后端校验\n        fetch('/api/v1/recaptcha/validate', {\n          method: 'POST',\n          body: JSON.stringify({ token }),\n        })\n          .then(resp =\u003e resp.json())\n          .then(result =\u003e {\n            if (result.statusCode === 100200 \u0026\u0026 result.data) {\n              console.log('签发的临时许可 token: %s, 请在五分钟内使用此token登录', result.data);\n            }\n          });\n      });\n    });\n  }\n\u003c/script\u003e\n```\n\n服务端:\n.env文件内 设置 COMPASS_RECAPTCHA_SECRET 环境变量为您在Google ReCaptcha注册的后台key\n\n1. 接口`/api/v1/recaptcha/validate`收到`{ token }`后会提交google验证\n2. 验证通过后会下发一个五分钟有效的临时token\n3. 用户在登录时可将用户信息与临时token一并提交登录接口\n4. 登录接口验证token属于签发的授权token并账号密码正确即登录成功\n\n### 接口多版本支持\n\n用法示例如下:\n\n```typescript\n@Controller('example')\nexport class ExampleController {\n  // 访问地址: /api/v1/example/test\n  @Get('test')\n  test(): string {\n    return 'This is v1 endpoint.';\n  }\n\n  // 访问地址: /api/v2/example/test\n  @Version('2')\n  @Get('test')\n  test2(): string {\n    return 'This is v2 endpoint.';\n  }\n}\n```\n\n### 接口限流保护\n\n默认一个IP一个端点每分钟仅允许调用20次,特例场景可以通过装饰器跳过限流或局部修改限流,示例如下:\n\n```typescript\n@Controller('example')\nexport class ExampleController {\n  // 该接口跳过节流保护\n  @SkipThrottle()\n  @Get('test')\n  test(): string {\n    return 'Hello world.';\n  }\n\n  // 该接口每分钟调用不超过3次\n  @Throttle(3, 60)\n  @Get('test2')\n  test(): string {\n    return 'Hello world.';\n  }\n\n  // 默认采用全局节流配置\n  @Get('test3')\n  test(): string {\n    return 'Hello world.';\n  }\n}\n```\n\n### 约束接口进参,移除非白名单属性,自动转换数据为符合预期的类型\n\n通过`shared/config/index.ts`下的validationOption可调整选项\n\n当遇见多个Dto联合类型时,内置ValidationPipe失效,可按照下列示例处理:\n\n```typescript\nimport { IsNumber, IsString, IsOptional } from 'class-validator';\nimport { Body } from '@nestjs/common';\nimport { validateMultipleDto } from '@shared';\n\nclass ADto {\n  @IsString()\n  id: string;\n}\n\nclass BDto {\n  @IsNumber()\n  age: number;\n}\n\nclass CDto {\n  @IsString()\n  name: string;\n\n  @IsOptional()\n  @IsString()\n  address?: string\n}\n\n@Controller('example')\nexport class ExampleController {\n  @Get('test')\n  test(@Body() body: ADto | BDto): string {\n    // 验证失败会抛出异常终止程序,第三个参数AND,OR来控制处理逻辑,默认是OR逻辑\n    validateMultipleDto(body, [ADto, BDto]);\n    return 'Hello world.';\n  }\n\n  /**\n   * @description 假如入参是 { name: 'test', test: 'test' }\n   * 实际body会是 { name: 'test' }, test属性会被自动移除\n   */\n  @Get('test2')\n  test2(@Body() body: CDto) {\n    return 'Hello world.';\n  }\n}\n```\n\n### \u003cspan id=\"prisma\"\u003ePrismaORM 数据库管理\u003c/span\u003e\n\u003e 请确保.env文件配置已经就绪\n\n####  如果你是新数据库\n\n使用 `npx prisma db push` 同步数据库架构\n\n同步数据库架构后执行`pnpm run seed`初始化数据库, 有问题或需要调整也可通过`pnpm run seed:rollback`回滚初始化动作\n\n#### 如果你是现有数据库架构\n\n`npx prisma db pull` 同步数据库架构到Prisma模型文件中\n\n`npx prisma format` 格式化schema文件\n\n`npx prisma generate` 生成Prisma Client文件\n\n#### 维护数据模型\n\n根据业务实际情况调整schema.prisma文件\n\n`npx prisma format` 格式化schema文件\n\n`npx prisma generate` 生成Prisma Client文件,每次scheme变更后都应执行\n\n`npx prisma-docs-generator serve` 基于generate的结果生成模型文档\n\n#### web浏览数据库数据\n\n`npx prisma studio` 通过web浏览数据库数据\n\n#### 迁移管理\n\n`npx prisma db push` 本地或开发环境可通过此命令直接同步数据库架构 警告: 请不要在测试或生产等正式环境使用此命令\n\n`npx prisma migrate dev --name [本次迁移的标题]` schema变更后创建迁移脚本\n\n`npx prisma migrate deploy` 执行迁移脚本\n\n`npx prisma migrate status` 查看当前迁移状态\n\n`npx prisma migrate resolve --rolled-back [migrate_name]` 回滚到指定记录位置\n\n当创建迁移文件后,如果你手动进行了迁移,可通过`npx prisma migrate resolve --applied [migrate_name]`将该次迁移手动标记为完成\n\n#### 隐私数据保护\n\n针对隐私数据入库及查询做二次加密保护,不可通过数据库直接查看隐私数据.\n\n`libs/db/src/db.service.ts` 内的useUserHook方法默认已对用户密码做不可逆加密入库\n\n不可逆加密隐私数据参考如下:\n\n```typescript\nimport { encodeMD5 } from '@shared';\n\nencodeMD5('password'); // 第二个参数为密钥, 默认取.env内的 COMPASS_PRIVACY_DATA_SECRET 值\n```\n\n### 统一的响应拦截器,规范返回数据\n\n在`shared/interceptors/response.interceptor.ts`定义的默认拦截逻辑,示例如下:\n\n```typescript\n@Controller('example')\nexport class ExampleController {\n  @Get('test')\n  test() {\n    return 'Hello world.'; // 实际响应: { statusCode: 100200, data: 'Hello world.', message: '请求成功' } HttpStatus = 200\n  }\n\n  @Get('test2')\n  test2() {\n    return new HttpResponse('Hello world.', { responseType: 'text' }); // 实际响应: 'Hello world.'\n  }\n  \n  @Get('test3')\n  test3() {\n    // 尽管是throw,但是客户端收到的返回依旧以HttpResponse配置为准,可以用来快捷中断程序逻辑执行,又控制响应的状态与数据\n    // 实际响应: { statusCode: 100400, data: 'Hello world.', message: '请求成功' } HttpStatus = 403\n    throw new HttpResponse('Hello world.', {\n      statusCode: ResponseCode.BAD_REQUEST,\n      httpStatus: HttpStatus.FORBIDDEN,\n    });\n  }\n}\n```\n\n### 支持JWT校验 + 用户权限集验证\n\n支持JWT授权,并按权限给予访问能力, 示例如下:\n\n```typescript\n@Controller('oauth')\nexport class OauthController {\n  constructor(\n    private jwtService: JwtService,\n    private oauthService: OauthService,\n  ) {}\n\n  @Public() // public装饰器指明该接口完全开放,跳过jwt验证,跳过权限验证\n  @Post('login')\n  async login(@Body() body: EMailLoginDto | TelephoneLoginDto) {\n    validateMultipleDto(body, [EMailLoginDto, TelephoneLoginDto]);\n    // 验证登录是否有效,通过后签发token\n    const result = await this.oauthService.validateLogin(body);\n    const signStr = this.jwtService.sign(result);\n    return { ...userInfo, token: signStr };\n  }\n\n  /**\n   * @description 该接口必须通过JWT验证后再通过用户权限验证,用户必须拥有对应权限\n   * Auth 接受两个参数,第一个参数类型必须是 PERMISSIONS | PERMISSIONS[]\n   * 第二个参数可选,类型是 'AND' | 'OR',默认是'AND',即所有声明的权限都必须具备,OR则代表声明的权限具备任意一个均可\n   * @param user @User()装饰器可以快捷的拿到授权通过后的用户信息数据\n   */\n  @Auth(PERMISSIONS.COMMON_USER_QUERY)\n  @Get('test')\n  async test(@User() user: unknown) {\n    return user;\n  }\n\n  /**\n   * @description 默认访问该接口必须先通过JWT验证\n   */\n  @Get('test2')\n  async test2(@User() user: any) {\n    return user;\n  }\n}\n```\n\n`shared/utils/jwt.strategy.ts` 内会根据用户所具备的角色去聚合用户权限集\n\n`shared/guards/jwt-auth.guard.ts` 具体处理用户访问权限的守卫\n\n### EMail邮件服务支持\n\n.env 文件内提供正确的 COMPASS_EMAIL_USER 及 COMPASS_EMAIL_PASSWORD 变量, 使用示例如下:\n\n```typescript\n// example.module.ts\nimport { EmailModule, EmailService } from '@app/email';\nimport { CompassEnv, getEnv } from '@shared';\n\nconst emailUser = getEnv(CompassEnv.EMAIL_USER);\nconst emailPassword = getEnv(CompassEnv.EMAIL_PASSWORD);\n\n@Module({\n  imports: [\n    // 默认使用outlook服务,请按需调整, 详见: https://nodemailer.com/usage/#setting-it-up\n    EmailModule.forRoot({\n      service: 'outlook365',\n      auth: {\n        user: emailUser,\n        pass: emailPassword,\n      },\n    }),\n  ],\n})\nexport class ExampleModule {}\n\n// example.service.ts\n@Injectable()\nexport class ExampleService {\n  constructor(private emailService: EmailService) {}\n  \n  sendEmailMsg(msg: string) {\n    // 具体参考 https://nodemailer.com/message/#common-fields\n    // 发出邮件\n    return this.emailService.sendMail({\n      from: SYSTEM_EMAIL_FROM, // 声明发送方\n      to: data.email, // 发送的目标\n      subject: '邮箱验证', // 主题\n      // 实际发送内容, replaceVariablesInString用来对模板内的变量做替换\n      html: replaceVariablesInString(EMAIL_CAPTCHA_TEMPLATE, {\n        context: 'Compass Service',\n        code: code.toString(),\n      }),\n    });\n  }\n}\n```\n\n### 支持连接redis服务\n\n按需调整.env文件内 COMPASS_REDIS_HOST,COMPASS_REDIS_PORT,COMPASS_REDIS_PASSWORD 等变量,使用示例如下:\n\n```typescript\n// example.service.ts\nimport { RedisManagerService, CAPTCHA_REDIS_KEY } from '@app/redis-manager';\n\n@Injectable()\nexport class ExampleService {\n  constructor(private redisService: RedisManagerService,) {}\n\n  async getCache(msg: string) {\n    // 具体参考 https://github.com/liaoliaots/nestjs-redis/blob/HEAD/docs/latest/redis.md\n    await this.redisService.get(CAPTCHA_REDIS_KEY, {\n      // 通过params替换CAPTCHA_REDIS_KEY内的变量值以定位到具体key\n      params: {\n        type: 'email',\n        account: user.email,\n      }\n    });\n  }\n  \n  async setCache() {\n    const code = random(100000, 999999);\n    // 将code码记入缓存\n    await this.redisService.set(CAPTCHA_REDIS_KEY, String(code), {\n      params: { type: 'email', account: data.email },\n    });\n  }\n}\n```\n\n### 添加日志中间件,监控入站请求\n\n默认会将所有入站请求打印在控制台,日志级别为log级.逻辑详见`shared/middleware/logger.middleware.ts`\n\n### 支持Swagger API文档\n\n`npm run start:dev` 或其他start启动项目后,访问/api/docs路径\n\n### helmet 安全的响应头设置\n\n在 `apps/compass-service/src/middleware/index.ts` 路径内启用\n\n### 默认启用 express json,urlencoded 中间件\n\n在 `apps/compass-service/src/middleware/express.middleware.ts` 内启用\n\n### 集成circleciCI自动集成部署\n\n详见`.circleci/config.yml`\n\n## 业务功能\n\n### 支持基于Chat gpt的AI对话功能\n### 支持授权管理\n### 支持待办事项管理\n### 支持权限管理(未完善)\n### 支持角色管理(未完善)\n### 支持用户管理(未完善)\n\n## 2.0 移除的特性\n\n* compression 移除,压缩支持应该在nginx层处理,而不在服务器\n* csurf 已废弃,不再采用\n* LoggerService 已移除,改为采用 @nestjs/common 内置的 Logger\n* 扩展的HttpException已被移除,改为采用 @nestjs/common 内置的 HttpException\n* SessionModule已被移除,这个模块并不适合在生产环境使用\n\n## 更多文档信息\n\n`npx prisma-docs-generator serve` 数据模型文档\n\n`npm run start:dev` 启动服务后访问: `http://localhost:8080/api/docs`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faiden-fe%2Fcompass-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faiden-fe%2Fcompass-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faiden-fe%2Fcompass-service/lists"}