likes
comments
collection
share

基于Midwayjs的管理系统-接口程序目录手册

作者站长头像
站长
· 阅读数 4

基于Midwayjs的管理系统-接口程序目录手册

项目目录结构

mask_api_midwayjs
├── assets                      目录-程序内部静态资源文件
├── script                      目录-程序可用脚本
├── src                         目录-源代码
├   ├── config                  目录-程序相关运行参数配置
├   ├── framework               目录-程序核心通用代码
├   ├── modules                 目录-业务模块
├   ├   ├── system              目录-系统内部接口模块
├   ├   ├   ├── controller      目录-接口路由控制层
├   ├   ├   ├── model           目录-数据对象模型层
├   ├   ├   ├── repository      目录-CURD数据存储层
├   ├   ├   ├── service         目录-业务逻辑服务层
├   ├   └── ...
├   ├── typings                 目录-程序通用类型定义
├   ├── configuration.ts        文件-程序框架启动入口
├   └── interface.ts            文件-程序通用接口函数自定义声明
├── test                        目录-测试单元
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .prettierrc.js
├── bootstrap.js                文件-程序部署PM2启动运行入口
├── jest.config.js
├── LICENSE
├── package.json                文件-程序依赖及启动命令信息
├── README.en.md
├── README.md
└── tsconfig.json               文件-typescript配置

以下根据目录名称区分节点

assets

可以使用 FileService 服务函数读取文件流

// src\framework\service\FileService.ts

/**
 * 内部文件读取 assets 目录
 * @param asserPath 内部文件相对地址,如:/template/excel/xxx.xlsx
 * @return 文件读取流
 */
readAssetsFileStream(asserPath: string): Promise<Buffer>

读取assets目录内文件 /template/excel/xxxx.xlsx

src/config

参考Midway官方-多环境配置

环境说明
default默认配置
local开发配置
prod部署配置
unittest测试配置(暂时没用)

一般只需要变更本地开发(local)和线上部署(prod)环境的配置文件,修改其内容会对默认(default)配置进行参数的覆盖。

src/framework

封装的各种工具类函数,应用于整个程序。

framework               目录-程序核心通用代码
├── cache               目录-缓存处理服务封装
├── constants           目录-常量属性值定义
├── datasource          目录-数据源处理服务封装
├── decorator           目录-装饰器定义
├── enums               目录-枚举类型定义
├── error-catch         目录-全局异常捕获拦截定义
├── middleware          目录-中间件定义
├── service             目录-服务函数定义
├── utils               目录-工具函数定义
├── vo                  目录-模型视图对象定义
└── ...

framework/cache

缓存服务封装:Redis

framework/constants

可复用常量值属性的定义

framework/datasource

数据库服务封装,根据 typeorm 中的配置获取数据源db实例,内置 execute 对原生sql操作

framework/decorator

可以改为中间件中实现,不一定要自定义装饰器。

Midway官方-自定义装饰器

方法装饰器

@PreAuthorize 用户身份授权认证校验

默认定义的接口都是不需要验证权限角色可以公开访问的,在定义接口访问的方法上面添加注解 @PreAuthorize 来控制访问权限。

超级管理员拥有所有权限,不受权限约束。

方法上使用注解示例:

/// 数据权限示例
@Get()
@PreAuthorize({ hasPermissions: ['system:user:query'] })
async getInfo(): Promise<String> { return "PreAuthorize Permissions OK!"}

// 只需含有其中一项权限 system:user:list 和 system:user:query
@PreAuthorize({ hasPermissions: ['system:user:list', 'system:user:query'] })

// 必须同时匹配含有其中权限 system:user:list 和 system:user:query
@PreAuthorize({ matchPermissions: ['system:user:list', 'system:user:query'] })


/// 角色权限示例
@Get()
@PreAuthorize({ hasRoles: ['user'] })
async getInfo(): Promise<String> { return "PreAuthorize Roles OK!"}

// 只需含有其中一项权限 user 和 admin
@PreAuthorize({ hasRoles: ['user', 'admin'] })

// 必须同时匹配含有其中角色 user 和 admin
@PreAuthorize({ matchRoles: ['user', 'admin'] })


/// 不需要验证权限角色,登录即可访问
@Get()
@PreAuthorize()
async getInfo(): Promise<String> { return "PreAuthorize OK!"}
@RepeatSubmit 防止表单重复提交

在定义接口访问方法上添加注解 @RepeatSubmit 来防止表单重复提交,限定单位时间内禁止相同内容重复请求。

方法上使用注解示例:

/// 防止重复提交示例
@Del('/refreshCache')
@RepeatSubmit(60)
async refreshCache(): Promise<String> { return "RepeatSubmit OK!" }

// 间隔时间(单位秒) 默认:5
@RepeatSubmit()

// 间隔时间设置60秒,同内容参数60秒内只能提交一次
@RepeatSubmit(60)
@RateLimit 限流

在定义接口访问方法上添加注解 @RateLimit 来限制单位时间内最多可以访问次数。

方法上使用注解示例:

/// 限流示例
@Get('/captchaImage')
@RateLimit({ time: 300, count: 60, limitType: LimitTypeEnum.IP })
async captchaImage(): Promise<Result> { return "RateLimit OK!" }

// 120秒内,最多请求60次,无类型时默认针对方法进行限制
@RateLimit({ time: 120, count: 60 })

// 300秒内,最多请求10次,指定类型针对访问者IP进行限制
@RateLimit({ time: 300, count: 10, limitType: LimitTypeEnum.IP })

// 720秒内,最多请求1000次,指定类型针对登录用户进行限制
@RateLimit({ time: 720, count: 1000, limitType: LimitTypeEnum.USER })
@OperLog 访问操作日志记录

在定义接口访问方法上添加注解 @OperLog 来对请求参数和响应数据进行记录。

请在用户身份授权认证校验后使用以便获取登录用户信息

方法上使用注解示例:

/// 访问操作日志记录示例
@Put()
@PreAuthorize()
@OperLog({
  title: '编辑信息',
  businessType: OperatorBusinessTypeEnum.UPDATE,
})
async edit(): Promise<Result> { return "RateLimit OK!" }

// 日志标题叫xx信息,日志类型为更新行为
@OperLog({ title: 'xx信息',  businessType: OperatorBusinessTypeEnum.UPDATE })

// 日志标题叫xx信息,日志类型为授权行为,操作类型为其他
@OperLog({ title: 'xx信息',  businessType: OperatorBusinessTypeEnum.GRANT, operatorType: OperatorTypeEnum.OTHER })

// 日志标题叫xx信息,日志类型为更新行为,操作类型默认为后台用户, 不记录请求参数,不记录响应参数
@OperLog({ title: 'xx信息',  businessType: OperatorBusinessTypeEnum.UPDATE, isSaveRequestData: false, isSaveResponseData: false })

framework/enums

枚举常量属性值,已选的枚举值映射

framework/error-catch

全局异常捕获并进行控制其响应信息

  • ForbiddenError 当前操作没有权限
  • UnauthorizedError 未授权认证用户
  • NotFoundError 页面未找到
  • DefaultErrorCatch 未知异常

framework/middleware

中间件洋葱模型,常用于验证日志等。

目前配置的中间有:

  • ReportMiddleware 请求响应日志
  • XssFilterMiddleware 跨站脚本XSS过滤
// 中间件补充...
// 路由将忽略此中间件
ignore(ctx: Context): boolean {
    return ctx.path === '/api/ignore'
}

// 忽略和匹配二选一

// 中间件补充...
// 匹配到的路由会执行此中间件
match(ctx: Context): boolean { 
  return ctx.path === '/api/match'
}

注意:忽略和匹配 不能同时使用否则会引起: options.match and options.ignore can not both present

framework/service

全局读取上下文与程序框架耦合的注解服务

framework/utils

可复用的函数工具,仅依赖npm库不与程序框架绑定使用的函数

framework/vo

程序内必要的模型视图数据结构

src/modules

按功能领域进行划分模块包,分层定义其实现

xxx                 目录-xxx模块
├── controller      目录-接口路由控制
├── model           目录-数据对象模型
├── repository      目录-CURD数据存储
├── service         目录-业务逻辑服务
├── processor       目录-队列任务处理
└── ...

xxx/controller

定义路由请求,负责参数接收验证并做一定格式转换后传递给业务逻辑服务(service)

xxx/model

很多时候是对数据库中表的字段属性进行对象模型建立,也可以是业务逻辑处理定义的数据结构模型

xxx/service

在使用时 ts 只能使用实现层的部分,所以定义接口后需要在 impl 目录中实现接口具体的函数行为。

service
├── impl                      目录-接口实现
├   ├── xxxImpl.ts            文件-xxx接口实现
├   └── ...
├── Ixxx.ts                   文件-xxx接口定义
└── ...

对请求参数进行逻辑操作的层面,会包含很多条件判断或调用其他依赖库等。

xxx/repository

在使用时 ts 只能使用实现层的部分,所以定义接口后需要在 impl 目录中实现接口具体的函数行为。

repository
├── impl                      目录-接口实现
├   ├── xxxImpl.ts            文件-xxx接口实现
├   └── ...
├── Ixxx.ts                   文件-xxx接口定义
└── ...

对服务层处理后,将数据传入进行存储。

为方便ORM切换重写其数据操作,只负责操作数据层面,一般对各表间存储关系进行关联操作。

数据库事务

在某些时候需要保证数据的一致性,对数据库的 SQL 操作。

常见场景:用户转账时,A用户-100 => B用户+100,中间发生异常时导致B没收到A的转账

import { Inject, Provide } from '@midwayjs/decorator';
import { DynamicDataSource } from '@/framework/datasource/DynamicDataSource';

@Provide()
@Singleton()
export class xxxRepositoryImpl {
  @Inject()
  private db: DynamicDataSource;

  /**
   * 更新操作
   * 
   * @returns true | false
   */
  async update_xxx(): Promise<boolean> {
    // 获取连接并创建新的queryRunner
    const queryRunner = this.db.executeRunner();

    // 对此事务执行一些操作
    try {
      // 开始事务
      await queryRunner.startTransaction();

      // sql语句
      let sql_str = `UPDATE tbl_xxx SET xx = ? WHERE id= ?`;

      const one = await queryRunner.query(sql_str, [101, "101"]);
      const one_rows = one.affectedRows;
      const two = await queryRunner.query(sql_str, [102, "102"]);
      const two_rows = two.affectedRows;

      if (parseInt(one_rows) <= 0 || parseInt(two_rows) <= 0) {
        // 有错误做出回滚更改 后释放连接
        await queryRunner.rollbackTransaction();
        return false;
      }

      // 提交事务写入库 后释放连接
      await queryRunner.commitTransaction();
      return true;
    } catch (err) {
      // 有错误做出回滚更改 后释放连接
      await queryRunner.rollbackTransaction();
      throw new Error('服务数据异常');
    } finally {
      //释放连接
      await queryRunner.release();
    }
  }

}

xxx/processor

声明执行方法函数逻辑后,在调度任务中创建指定调用目标执行方法名称(simple)。

import { Processor, IProcessor, Context } from '@midwayjs/bull';
import { Inject } from '@midwayjs/decorator';

/**
 * 简单示例 队列任务处理
 */
@Processor('simple')
export class SimpleProcessor implements IProcessor {
  @Inject()
  private ctx: Context;

  async execute(options: any): Promise<ProcessorData> {
    const log = this.ctx.getLogger();
    const ctxJob = this.ctx.job;

    // 执行一次得到是直接得到传入的jobId
    // 重复任务得到编码格式的jobId => repeat:编码Jobid:执行时间戳
    // options 获取任务执行时外部给到的参数数据
    // log 日志输出到bull配置的文件内
    // ctxJob 当前任务的上下文,可控制暂停进度等数据

    log.info('当前jobId %s', ctxJob.id);

    // 返回结果,用于记录执行结果
    return options;
  }
}

src/typings

全局范围的数据结构类型声明定义

test

程序测试包(暂时没有使用)