https://github.com/aiden-fe/compass-service
Nestjs backend service.
https://github.com/aiden-fe/compass-service
alicloud-sms express nestjs nodejs nodemailer passport passport-jwt prisma prisma-client template typescript
Last synced: 9 months ago
JSON representation
Nestjs backend service.
- Host: GitHub
- URL: https://github.com/aiden-fe/compass-service
- Owner: Aiden-FE
- License: mit
- Created: 2022-04-23T07:44:55.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2023-05-11T06:49:47.000Z (over 2 years ago)
- Last Synced: 2025-03-25T07:36:09.784Z (10 months ago)
- Topics: alicloud-sms, express, nestjs, nodejs, nodemailer, passport, passport-jwt, prisma, prisma-client, template, typescript
- Language: TypeScript
- Homepage:
- Size: 433 KB
- Stars: 6
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
* [compass-service 2.0](#compass-service-20)
* [项目启动](#项目启动)
* [特性](#特性)
* [Monorepo 结构,易于扩展服务及公共资源沉淀](#monorepo-结构易于扩展服务及公共资源沉淀)
* [Typescript/Jest/Airbnb Eslint/Prettier](#typescriptjestairbnb-eslintprettier)
* [支持环境变量控制](#支持环境变量控制)
* [支持Google reCAPTCHA v3 人机校验](#支持google-recaptcha-v3-人机校验)
* [接口多版本支持](#接口多版本支持)
* [接口限流保护](#接口限流保护)
* [约束接口进参,移除非白名单属性,自动转换数据为符合预期的类型](#约束接口进参移除非白名单属性自动转换数据为符合预期的类型)
* [PrismaORM 数据库管理](#span-idprisma-prismaorm-数据库管理-span)
* [如果你是新数据库](#如果你是新数据库)
* [如果你是现有数据库架构](#如果你是现有数据库架构)
* [维护数据模型](#维护数据模型)
* [web浏览数据库数据](#web浏览数据库数据)
* [迁移管理](#迁移管理)
* [隐私数据保护](#隐私数据保护)
* [统一的响应拦截器,规范返回数据](#统一的响应拦截器规范返回数据)
* [支持JWT校验 + 用户权限集验证](#支持jwt校验--用户权限集验证)
* [EMail邮件服务支持](#email邮件服务支持)
* [支持连接redis服务](#支持连接redis服务)
* [添加日志中间件,监控入站请求](#添加日志中间件监控入站请求)
* [支持Swagger API文档](#支持swagger-api文档)
* [helmet 安全的响应头设置](#helmet-安全的响应头设置)
* [默认启用 express json,urlencoded 中间件](#默认启用-express-jsonurlencoded-中间件)
* [集成circleciCI自动集成部署](#集成circlecici自动集成部署)
* [业务功能](#业务功能)
* [支持基于Chat gpt的AI对话功能](#支持基于chat-gpt的ai对话功能)
* [支持授权管理](#支持授权管理)
* [支持待办事项管理](#支持待办事项管理)
* [支持权限管理(未完善)](#支持权限管理--未完善-)
* [支持角色管理(未完善)](#支持角色管理--未完善-)
* [支持用户管理(未完善)](#支持用户管理--未完善-)
* [2.0 移除的特性](#20-移除的特性)
* [更多文档信息](#更多文档信息)
# compass-service 2.0
本项目会以具体业务为实例不断完善内容,如果你希望使用本项目作为基础模板快速搭建项目,可使用[Compass Template](https://github.com/Aiden-FE/compass-template)内的Nest模板
## 项目启动
> 首次启动请先参考下方 [Prisma ORM 管理](#prisma) 部分同步数据库架构
>
> 如果您不具备可用的mysql,redis或是可选的postgres环境,可以参考[部署基础环境](./deploy/README.md#部署基础环境)一键启动基础环境
`npm install` 安装依赖
根目录新建 .env 文件, 复制 .env.example 内容到 .env 文件,并按需调整配置内容.
`npm run start:dev` 开发模式启动
`npm run start:prod` 生产模式启动
`npm run format` 执行代码格式化
`npm run lint` 执行代码检查
根据自身业务实际情况,修改项目内 "FIXME: " 标记部分的逻辑
`npm run build` 构建项目
## 特性
### Monorepo 结构,易于扩展服务及公共资源沉淀
`nest g app [project_name]` 创建一个子应用到monorepo
`nest g lib [library_name]` 创建一个包到monorepo
`./shared` 文件夹内直接添加可被共享的资源文件
### Typescript/Jest/Airbnb Eslint/Prettier
* 支持Typescript环境
* `npm run format` 进行代码格式化
* `npm run lint` 进行代码检查,默认基于Airbnb规范
* `npm run test` 进行单元测试
* `npm run test:e2e` 进行端到端测试
### 支持环境变量控制
支持.env文件控制环境变量,示例可见: .env.example,复制示例文件进入.env文件后按需配置即可
### 支持Google reCAPTCHA v3 人机校验
客户端:
```html
// 当点击某个提交按钮时进行人机静默校验,否则应该进行双重认证或拒绝认证
function onClick(e) {
e.preventDefault();
// 提示: reCAPTCHA_site_key为您在Google ReCaptcha注册的网站key
grecaptcha.ready(function() {
// action各种含义参考: https://developers.google.com/recaptcha/docs/v3?hl=zh-cn#interpreting_the_score
grecaptcha.execute('reCAPTCHA_site_key', {action: 'login'}).then(function(token) {
// 在此处添加您的逻辑,把表单数据跟token一起提供给后端校验
fetch('/api/v1/recaptcha/validate', {
method: 'POST',
body: JSON.stringify({ token }),
})
.then(resp => resp.json())
.then(result => {
if (result.statusCode === 100200 && result.data) {
console.log('签发的临时许可 token: %s, 请在五分钟内使用此token登录', result.data);
}
});
});
});
}
```
服务端:
.env文件内 设置 COMPASS_RECAPTCHA_SECRET 环境变量为您在Google ReCaptcha注册的后台key
1. 接口`/api/v1/recaptcha/validate`收到`{ token }`后会提交google验证
2. 验证通过后会下发一个五分钟有效的临时token
3. 用户在登录时可将用户信息与临时token一并提交登录接口
4. 登录接口验证token属于签发的授权token并账号密码正确即登录成功
### 接口多版本支持
用法示例如下:
```typescript
@Controller('example')
export class ExampleController {
// 访问地址: /api/v1/example/test
@Get('test')
test(): string {
return 'This is v1 endpoint.';
}
// 访问地址: /api/v2/example/test
@Version('2')
@Get('test')
test2(): string {
return 'This is v2 endpoint.';
}
}
```
### 接口限流保护
默认一个IP一个端点每分钟仅允许调用20次,特例场景可以通过装饰器跳过限流或局部修改限流,示例如下:
```typescript
@Controller('example')
export class ExampleController {
// 该接口跳过节流保护
@SkipThrottle()
@Get('test')
test(): string {
return 'Hello world.';
}
// 该接口每分钟调用不超过3次
@Throttle(3, 60)
@Get('test2')
test(): string {
return 'Hello world.';
}
// 默认采用全局节流配置
@Get('test3')
test(): string {
return 'Hello world.';
}
}
```
### 约束接口进参,移除非白名单属性,自动转换数据为符合预期的类型
通过`shared/config/index.ts`下的validationOption可调整选项
当遇见多个Dto联合类型时,内置ValidationPipe失效,可按照下列示例处理:
```typescript
import { IsNumber, IsString, IsOptional } from 'class-validator';
import { Body } from '@nestjs/common';
import { validateMultipleDto } from '@shared';
class ADto {
@IsString()
id: string;
}
class BDto {
@IsNumber()
age: number;
}
class CDto {
@IsString()
name: string;
@IsOptional()
@IsString()
address?: string
}
@Controller('example')
export class ExampleController {
@Get('test')
test(@Body() body: ADto | BDto): string {
// 验证失败会抛出异常终止程序,第三个参数AND,OR来控制处理逻辑,默认是OR逻辑
validateMultipleDto(body, [ADto, BDto]);
return 'Hello world.';
}
/**
* @description 假如入参是 { name: 'test', test: 'test' }
* 实际body会是 { name: 'test' }, test属性会被自动移除
*/
@Get('test2')
test2(@Body() body: CDto) {
return 'Hello world.';
}
}
```
### PrismaORM 数据库管理
> 请确保.env文件配置已经就绪
#### 如果你是新数据库
使用 `npx prisma db push` 同步数据库架构
同步数据库架构后执行`pnpm run seed`初始化数据库, 有问题或需要调整也可通过`pnpm run seed:rollback`回滚初始化动作
#### 如果你是现有数据库架构
`npx prisma db pull` 同步数据库架构到Prisma模型文件中
`npx prisma format` 格式化schema文件
`npx prisma generate` 生成Prisma Client文件
#### 维护数据模型
根据业务实际情况调整schema.prisma文件
`npx prisma format` 格式化schema文件
`npx prisma generate` 生成Prisma Client文件,每次scheme变更后都应执行
`npx prisma-docs-generator serve` 基于generate的结果生成模型文档
#### web浏览数据库数据
`npx prisma studio` 通过web浏览数据库数据
#### 迁移管理
`npx prisma db push` 本地或开发环境可通过此命令直接同步数据库架构 警告: 请不要在测试或生产等正式环境使用此命令
`npx prisma migrate dev --name [本次迁移的标题]` schema变更后创建迁移脚本
`npx prisma migrate deploy` 执行迁移脚本
`npx prisma migrate status` 查看当前迁移状态
`npx prisma migrate resolve --rolled-back [migrate_name]` 回滚到指定记录位置
当创建迁移文件后,如果你手动进行了迁移,可通过`npx prisma migrate resolve --applied [migrate_name]`将该次迁移手动标记为完成
#### 隐私数据保护
针对隐私数据入库及查询做二次加密保护,不可通过数据库直接查看隐私数据.
`libs/db/src/db.service.ts` 内的useUserHook方法默认已对用户密码做不可逆加密入库
不可逆加密隐私数据参考如下:
```typescript
import { encodeMD5 } from '@shared';
encodeMD5('password'); // 第二个参数为密钥, 默认取.env内的 COMPASS_PRIVACY_DATA_SECRET 值
```
### 统一的响应拦截器,规范返回数据
在`shared/interceptors/response.interceptor.ts`定义的默认拦截逻辑,示例如下:
```typescript
@Controller('example')
export class ExampleController {
@Get('test')
test() {
return 'Hello world.'; // 实际响应: { statusCode: 100200, data: 'Hello world.', message: '请求成功' } HttpStatus = 200
}
@Get('test2')
test2() {
return new HttpResponse('Hello world.', { responseType: 'text' }); // 实际响应: 'Hello world.'
}
@Get('test3')
test3() {
// 尽管是throw,但是客户端收到的返回依旧以HttpResponse配置为准,可以用来快捷中断程序逻辑执行,又控制响应的状态与数据
// 实际响应: { statusCode: 100400, data: 'Hello world.', message: '请求成功' } HttpStatus = 403
throw new HttpResponse('Hello world.', {
statusCode: ResponseCode.BAD_REQUEST,
httpStatus: HttpStatus.FORBIDDEN,
});
}
}
```
### 支持JWT校验 + 用户权限集验证
支持JWT授权,并按权限给予访问能力, 示例如下:
```typescript
@Controller('oauth')
export class OauthController {
constructor(
private jwtService: JwtService,
private oauthService: OauthService,
) {}
@Public() // public装饰器指明该接口完全开放,跳过jwt验证,跳过权限验证
@Post('login')
async login(@Body() body: EMailLoginDto | TelephoneLoginDto) {
validateMultipleDto(body, [EMailLoginDto, TelephoneLoginDto]);
// 验证登录是否有效,通过后签发token
const result = await this.oauthService.validateLogin(body);
const signStr = this.jwtService.sign(result);
return { ...userInfo, token: signStr };
}
/**
* @description 该接口必须通过JWT验证后再通过用户权限验证,用户必须拥有对应权限
* Auth 接受两个参数,第一个参数类型必须是 PERMISSIONS | PERMISSIONS[]
* 第二个参数可选,类型是 'AND' | 'OR',默认是'AND',即所有声明的权限都必须具备,OR则代表声明的权限具备任意一个均可
* @param user @User()装饰器可以快捷的拿到授权通过后的用户信息数据
*/
@Auth(PERMISSIONS.COMMON_USER_QUERY)
@Get('test')
async test(@User() user: unknown) {
return user;
}
/**
* @description 默认访问该接口必须先通过JWT验证
*/
@Get('test2')
async test2(@User() user: any) {
return user;
}
}
```
`shared/utils/jwt.strategy.ts` 内会根据用户所具备的角色去聚合用户权限集
`shared/guards/jwt-auth.guard.ts` 具体处理用户访问权限的守卫
### EMail邮件服务支持
.env 文件内提供正确的 COMPASS_EMAIL_USER 及 COMPASS_EMAIL_PASSWORD 变量, 使用示例如下:
```typescript
// example.module.ts
import { EmailModule, EmailService } from '@app/email';
import { CompassEnv, getEnv } from '@shared';
const emailUser = getEnv(CompassEnv.EMAIL_USER);
const emailPassword = getEnv(CompassEnv.EMAIL_PASSWORD);
@Module({
imports: [
// 默认使用outlook服务,请按需调整, 详见: https://nodemailer.com/usage/#setting-it-up
EmailModule.forRoot({
service: 'outlook365',
auth: {
user: emailUser,
pass: emailPassword,
},
}),
],
})
export class ExampleModule {}
// example.service.ts
@Injectable()
export class ExampleService {
constructor(private emailService: EmailService) {}
sendEmailMsg(msg: string) {
// 具体参考 https://nodemailer.com/message/#common-fields
// 发出邮件
return this.emailService.sendMail({
from: SYSTEM_EMAIL_FROM, // 声明发送方
to: data.email, // 发送的目标
subject: '邮箱验证', // 主题
// 实际发送内容, replaceVariablesInString用来对模板内的变量做替换
html: replaceVariablesInString(EMAIL_CAPTCHA_TEMPLATE, {
context: 'Compass Service',
code: code.toString(),
}),
});
}
}
```
### 支持连接redis服务
按需调整.env文件内 COMPASS_REDIS_HOST,COMPASS_REDIS_PORT,COMPASS_REDIS_PASSWORD 等变量,使用示例如下:
```typescript
// example.service.ts
import { RedisManagerService, CAPTCHA_REDIS_KEY } from '@app/redis-manager';
@Injectable()
export class ExampleService {
constructor(private redisService: RedisManagerService,) {}
async getCache(msg: string) {
// 具体参考 https://github.com/liaoliaots/nestjs-redis/blob/HEAD/docs/latest/redis.md
await this.redisService.get(CAPTCHA_REDIS_KEY, {
// 通过params替换CAPTCHA_REDIS_KEY内的变量值以定位到具体key
params: {
type: 'email',
account: user.email,
}
});
}
async setCache() {
const code = random(100000, 999999);
// 将code码记入缓存
await this.redisService.set(CAPTCHA_REDIS_KEY, String(code), {
params: { type: 'email', account: data.email },
});
}
}
```
### 添加日志中间件,监控入站请求
默认会将所有入站请求打印在控制台,日志级别为log级.逻辑详见`shared/middleware/logger.middleware.ts`
### 支持Swagger API文档
`npm run start:dev` 或其他start启动项目后,访问/api/docs路径
### helmet 安全的响应头设置
在 `apps/compass-service/src/middleware/index.ts` 路径内启用
### 默认启用 express json,urlencoded 中间件
在 `apps/compass-service/src/middleware/express.middleware.ts` 内启用
### 集成circleciCI自动集成部署
详见`.circleci/config.yml`
## 业务功能
### 支持基于Chat gpt的AI对话功能
### 支持授权管理
### 支持待办事项管理
### 支持权限管理(未完善)
### 支持角色管理(未完善)
### 支持用户管理(未完善)
## 2.0 移除的特性
* compression 移除,压缩支持应该在nginx层处理,而不在服务器
* csurf 已废弃,不再采用
* LoggerService 已移除,改为采用 @nestjs/common 内置的 Logger
* 扩展的HttpException已被移除,改为采用 @nestjs/common 内置的 HttpException
* SessionModule已被移除,这个模块并不适合在生产环境使用
## 更多文档信息
`npx prisma-docs-generator serve` 数据模型文档
`npm run start:dev` 启动服务后访问: `http://localhost:8080/api/docs`