123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- ---
- description: 控制器(Controller)
- globs:
- ---
- # 控制器(Controller)
- 为了实现`快速CRUD`与`自动路由`功能,框架基于[midwayjs controller](mdc:https:/www.midwayjs.org/docs/controller),进行改造加强
- 完全继承[midwayjs controller](mdc:https:/www.midwayjs.org/docs/controller)的所有功能
- `快速CRUD`与`自动路由`,大大提高编码效率与编码量
- ## 路由前缀
- 虽然可以手动设置,但是我们并不推荐,cool-admin 在全局权限校验包含一定的规则,
- 如果你没有很了解框架原理手动设置可能产生部分功能失效的问题
- ### 手动
- `/api/other`
- 无通用 CRUD 设置方法
- ```ts
- import { CoolController, BaseController } from "@cool-midway/core";
- /**
- * 商品
- */
- @CoolController("/api")
- export class AppDemoGoodsController extends BaseController {
- /**
- * 其他接口
- */
- @Get("/other")
- async other() {
- return this.ok("hello, cool-admin!!!");
- }
- }
- ```
- 含通用 CRUD 配置方法
- ```ts
- import { Get } from "@midwayjs/core";
- import { CoolController, BaseController } from "@cool-midway/core";
- import { DemoGoodsEntity } from "../../entity/goods";
- /**
- * 商品
- */
- @CoolController({
- prefix: "/api",
- api: ["add", "delete", "update", "info", "list", "page"],
- entity: DemoGoodsEntity,
- })
- export class AppDemoGoodsController extends BaseController {
- /**
- * 其他接口
- */
- @Get("/other")
- async other() {
- return this.ok("hello, cool-admin!!!");
- }
- }
- ```
- ### 自动
- 大多数情况下你无需指定自己的路由前缀,路由前缀将根据规则自动生成。
- ::: warning 警告
- 自动路由只影响模块中的 controller,其他位置建议不要使用
- :::
- `src/modules/demo/controller/app/goods.ts`
- 路由前缀是根据文件目录文件名按照[规则](mdc:src/guide/core/controller.html#规则)生成的,上述示例生成的路由为
- `http://127.0.0.1:8001/app/demo/goods/xxx`
- `xxx`代表具体的方法,如: `add`、`page`、`other`
- ```ts
- import { Get } from "@midwayjs/core";
- import { CoolController, BaseController } from "@cool-midway/core";
- import { DemoGoodsEntity } from "../../entity/goods";
- /**
- * 商品
- */
- @CoolController({
- api: ["add", "delete", "update", "info", "list", "page"],
- entity: DemoGoodsEntity,
- })
- export class AppDemoGoodsController extends BaseController {
- /**
- * 其他接口
- */
- @Get("/other")
- async other() {
- return this.ok("hello, cool-admin!!!");
- }
- }
- ```
- ### 规则
- /controller 文件夹下的文件夹名或者文件名/模块文件夹名/方法名
- #### 举例
- ```ts
- // 模块目录
- ├── modules
- │ └── demo(模块名)
- │ │ └── controller(api接口)
- │ │ │ └── app(参数校验)
- │ │ │ │ └── goods.ts(商品的controller)
- │ │ │ └── pay.ts(支付的controller)
- │ │ └── config.ts(必须,模块的配置)
- │ │ └── init.sql(可选,初始化该模块的sql)
- ```
- 生成的路由前缀为:
- `/pay/demo/xxx(具体的方法)`与`/app/demo/goods/xxx(具体的方法)`
- ## CRUD
- ### 参数配置(CurdOption)
- 通用增删改查配置参数
- | 参数 | 类型 | 说明 | 备注 |
- | ------------------ | -------- | ------------------------------------------------------------- | ---- |
- | prefix | String | 手动设置路由前缀 | |
- | api | Array | 快速 API 接口可选`add` `delete` `update` `info` `list` `page` | |
- | serviceApis | Array | 将 service 方法注册为 api,通过 post 请求,直接调用 service 方法 | |
- | pageQueryOp | QueryOp | 分页查询设置 | |
- | listQueryOp | QueryOp | 列表查询设置 | |
- | insertParam | Function | 请求插入参数,如新增的时候需要插入当前登录用户的 ID | |
- | infoIgnoreProperty | Array | `info`接口忽略返回的参数,如用户信息不想返回密码 | |
- ### 查询配置(QueryOp)
- 分页查询与列表查询配置参数
- | 参数 | 类型 | 说明 | 备注 |
- | ----------------- | -------- | ----------------------------------------------------------------------------------- | ---- |
- | keyWordLikeFields | Array | 支持模糊查询的字段,如一个表中的`name`字段需要模糊查询 | |
- | where | Function | 其他查询条件 | |
- | select | Array | 选择查询字段 | |
- | fieldEq | Array | 筛选字段,字符串数组或者对象数组{ column: string, requestParam: string },如 type=1 | |
- | fieldLike | Array | 模糊查询字段,字符串数组或者对象数组{ column: string, requestParam: string },如 title | |
- | addOrderBy | Object | 排序 | |
- | join | JoinOp[] | 关联表查询 | |
- ### 关联表(JoinOp)
- 关联表查询配置参数
- | 参数 | 类型 | 说明 |
- | --------- | ------ | ------------------------------------------------------------------ |
- | entity | Class | 实体类,注意不能写表名 |
- | alias | String | 别名,如果有关联表默认主表的别名为`a`, 其他表一般按 b、c、d...设置 |
- | condition | String | 关联条件 |
- | type | String | 内关联: 'innerJoin', 左关联:'leftJoin' |
- ### 完整示例
- ```ts
- import { Get } from "@midwayjs/core";
- import { CoolController, BaseController } from "@cool-midway/core";
- import { BaseSysUserEntity } from "../../../base/entity/sys/user";
- import { DemoAppGoodsEntity } from "../../entity/goods";
- /**
- * 商品
- */
- @CoolController({
- // 添加通用CRUD接口
- api: ["add", "delete", "update", "info", "list", "page"],
- // 8.x新增,将service方法注册为api,通过post请求,直接调用service方法
- serviceApis: [
- 'use',
- {
- method: 'test1',
- summary: '不使用多租户', // 接口描述
- },
- 'test2', // 也可以不设置summary
- ]
- // 设置表实体
- entity: DemoAppGoodsEntity,
- // 向表插入当前登录用户ID
- insertParam: (ctx) => {
- return {
- // 获得当前登录的后台用户ID,需要请求头传Authorization参数
- userId: ctx.admin.userId,
- };
- },
- // 操作crud之前做的事情 @cool-midway/core@3.2.14 新增
- before: (ctx) => {
- // 将前端的数据转JSON格式存数据库
- const { data } = ctx.request.body;
- ctx.request.body.data = JSON.stringify(data);
- },
- // info接口忽略价格字段
- infoIgnoreProperty: ["price"],
- // 分页查询配置
- pageQueryOp: {
- // 让title字段支持模糊查询
- keyWordLikeFields: ["title"],
- // 让type字段支持筛选,请求筛选字段与表字段一致是情况
- fieldEq: ["type"],
- // 多表关联,请求筛选字段与表字段不一致的情况
- fieldEq: [{ column: "a.id", requestParam: "id" }],
- // 让title字段支持模糊查询,请求参数为title
- fieldLike: ['a.title'],
- // 让title字段支持模糊查询,请求筛选字段与表字段不一致的情况
- fieldLike: [{ column: "a.title", requestParam: "title" }],
- // 指定返回字段,注意多表查询这个是必要的,否则会出现重复字段的问题
- select: ["a.*", "b.name", "a.name AS userName"],
- // 4.x置为过时 改用 join 关联表用户表
- leftJoin: [
- {
- entity: BaseSysUserEntity,
- alias: "b",
- condition: "a.userId = b.id",
- },
- ],
- // 4.x新增
- join: [
- {
- entity: BaseSysUserEntity,
- alias: "b",
- condition: "a.userId = b.id",
- type: "innerJoin",
- },
- ],
- // 4.x 新增 追加其他条件
- extend: async (find: SelectQueryBuilder<DemoGoodsEntity>) => {
- find.groupBy("a.id");
- },
- // 增加其他条件
- where: async (ctx) => {
- // 获取body参数
- const { a } = ctx.request.body;
- return [
- // 价格大于90
- ["a.price > :price", { price: 90.0 }],
- // 满足条件才会执行
- ["a.price > :price", { price: 90.0 }, "条件"],
- // 多个条件一起
- [
- "(a.price = :price or a.userId = :userId)",
- { price: 90.0, userId: ctx.admin.userId },
- ],
- ];
- },
- // 添加排序
- addOrderBy: {
- price: "desc",
- },
- },
- })
- export class DemoAppGoodsController extends BaseController {
- /**
- * 其他接口
- */
- @Get("/other")
- async other() {
- return this.ok("hello, cool-admin!!!");
- }
- }
- ```
- ::: warning
- 如果是多表查询,必须设置 select 参数,否则会出现重复字段的错误,因为每个表都继承了 BaseEntity,至少都有 id、createTime、updateTime 三个相同的字段。
- :::
- 通过这一波操作之后,我们的商品接口的功能已经很强大了,除了通用的 CRUD,我们的接口还支持多种方式的数据筛选
- ### 获得 ctx 对象
- ```ts
- @CoolController(
- {
- api: ['add', 'delete', 'update', 'info', 'list', 'page'],
- entity: DemoAppGoodsEntity,
- // 获得ctx对象
- listQueryOp: ctx => {
- return new Promise<QueryOp>(res => {
- res({
- fieldEq: [],
- });
- });
- },
- // 获得ctx对象
- pageQueryOp: ctx => {
- return new Promise<QueryOp>(res => {
- res({
- fieldEq: [],
- });
- });
- },
- },
- {
- middleware: [],
- }
- )
- ```
- ### 接口调用
- `add` `delete` `update` `info` 等接口可以用法[参照快速开始](mdc:src/guide/quick.html#接口调用)
- 这里详细说明下`page` `list`两个接口的调用方式,这两个接口调用方式差不多,一个是分页一个是非分页。
- 以`page`接口为例
- #### 分页
- POST `/admin/demo/goods/page` 分页数据
- **请求**
- Url: http://127.0.0.1:8001/admin/demo/goods/page
- Method: POST
- #### Body
- ```json
- {
- "keyWord": "商品标题", // 模糊搜索,搜索的字段对应keyWordLikeFields
- "type": 1, // 全等于筛选,对应fieldEq
- "page": 2, // 第几页
- "size": 1, // 每页返回个数
- "sort": "desc", // 排序方向
- "order": "id" // 排序字段
- }
- ```
- **返回**
- ```json
- {
- "code": 1000,
- "message": "success",
- "data": {
- "list": [
- {
- "id": 4,
- "createTime": "2021-03-12 16:23:46",
- "updateTime": "2021-03-12 16:23:46",
- "title": "这是一个商品2",
- "pic": "https://show.cool-admin.com/uploads/20210311/2e393000-8226-11eb-abcf-fd7ae6caeb70.png",
- "price": "99.00",
- "userId": 1,
- "type": 1,
- "name": "超级管理员"
- }
- ],
- "pagination": {
- "page": 2,
- "size": 1,
- "total": 4
- }
- }
- }
- ```
- ### 服务注册成 Api
- 很多情况下,我们在`Controller`层并不想过多地操作,而是想直接调用`Service`层的方法,这个时候我们可以将`Service`层的方法注册成`Api`,那么你的某个`Service`方法就变成了`Api`。
- #### 示例:
- 在 Controller 中
- ```ts
- import { CoolController, BaseController } from "@cool-midway/core";
- import { DemoGoodsEntity } from "../../entity/goods";
- import { DemoTenantService } from "../../service/tenant";
- /**
- * 示例
- */
- @CoolController({
- serviceApis: [
- "use",
- {
- method: "test1",
- summary: "不使用多租户", // 接口描述
- },
- "test2", // 也可以不设置summary
- ],
- entity: DemoGoodsEntity,
- service: DemoXxxService,
- })
- export class AdminDemoTenantController extends BaseController {}
- ```
- 在 Service 中
- ```ts
- /**
- * 示例服务
- */
- @Provide()
- export class DemoXxxService extends BaseService {
- /**
- * 示例方法1
- */
- async test1(params) {
- console.log(params);
- return "test1";
- }
- /**
- * 示例方法2
- */
- async test2() {
- return "test2";
- }
- }
- ```
- ::: warning 注意
- `serviceApis` 注册为`Api`的请求方法是`POST`,所以`Service`层的方法参数需要通过`body`传递
- :::
- ### 重写 CRUD 实现
- 在实际开发过程中,除了这些通用的接口可以满足大部分的需求,但是也有一些特殊的需求无法满足用户要求,这个时候也可以重写`add` `delete` `update` `info` `list` `page` 的实现
- #### 编写 service
- 在模块新建 service 文件夹(名称非强制性),再新建一个`service`实现,继承框架的`BaseService`
- ```ts
- import { Inject, Provide } from "@midwayjs/core";
- import { BaseService } from "@cool-midway/core";
- import { InjectEntityModel } from "@midwayjs/orm";
- import { Repository } from "typeorm";
- import { BaseSysMenuEntity } from "../../entity/sys/menu";
- import * as _ from "lodash";
- import { BaseSysPermsService } from "./perms";
- /**
- * 菜单
- */
- @Provide()
- export class BaseSysMenuService extends BaseService {
- @Inject()
- ctx;
- @InjectEntityModel(BaseSysMenuEntity)
- baseSysMenuEntity: Repository<BaseSysMenuEntity>;
- @Inject()
- baseSysPermsService: BaseSysPermsService;
- /**
- * 重写list实现
- */
- async list() {
- const menus = await this.getMenus(
- this.ctx.admin.roleIds,
- this.ctx.admin.username === "admin"
- );
- if (!_.isEmpty(menus)) {
- menus.forEach((e) => {
- const parentMenu = menus.filter((m) => {
- e.parentId = parseInt(e.parentId);
- if (e.parentId == m.id) {
- return m.name;
- }
- });
- if (!_.isEmpty(parentMenu)) {
- e.parentName = parentMenu[0].name;
- }
- });
- }
- return menus;
- }
- }
- ```
- #### 设置服务实现
- `CoolController`设置自己的服务实现
- ```ts
- import { Inject } from "@midwayjs/core";
- import { CoolController, BaseController } from "@cool-midway/core";
- import { BaseSysMenuEntity } from "../../../entity/sys/menu";
- import { BaseSysMenuService } from "../../../service/sys/menu";
- /**
- * 菜单
- */
- @CoolController({
- api: ["add", "delete", "update", "info", "list", "page"],
- entity: BaseSysMenuEntity,
- service: BaseSysMenuService,
- })
- export class BaseSysMenuController extends BaseController {
- @Inject()
- baseSysMenuService: BaseSysMenuService;
- }
- ```
- ## 路由标签
- 我们经常有这样的需求:给某个请求地址打上标记,如忽略 token,忽略签名等。
- ```ts
- import { Get, Inject } from "@midwayjs/core";
- import {
- CoolController,
- BaseController,
- CoolUrlTag,
- TagTypes,
- CoolUrlTagData,
- } from "@cool-midway/core";
- /**
- * 测试给URL打标签
- */
- @CoolController({
- api: [],
- entity: "",
- pageQueryOp: () => {},
- })
- // add 接口忽略token
- @CoolUrlTag({
- key: TagTypes.IGNORE_TOKEN,
- value: ["add"],
- })
- export class DemoAppTagController extends BaseController {
- @Inject()
- tag: CoolUrlTagData;
- /**
- * 获得标签数据, 如可以标记忽略token的url,然后在中间件判断
- * @returns
- */
- // 这是6.x支持的,可以直接标记这个接口忽略token,更加灵活优雅,但是记得配合@CoolUrlTag()一起使用,也就是Controller上要有这个注解,@CoolTag才会生效
- @CoolTag(TagTypes.IGNORE_TOKEN)
- @Get("/data")
- async data() {
- return this.ok(this.tag.byKey(TagTypes.IGNORE_TOKEN));
- }
- }
- ```
- #### 中间件
- ```ts
- import { CoolUrlTagData, TagTypes } from "@cool-midway/core";
- import { IMiddleware } from "@midwayjs/core";
- import { Inject, Middleware } from "@midwayjs/core";
- import { NextFunction, Context } from "@midwayjs/koa";
- @Middleware()
- export class DemoMiddleware implements IMiddleware<Context, NextFunction> {
- @Inject()
- tag: CoolUrlTagData;
- resolve() {
- return async (ctx: Context, next: NextFunction) => {
- const urls = this.tag.byKey(TagTypes.IGNORE_TOKEN);
- console.log("忽略token的URL数组", urls);
- // 这里可以拿到下一个中间件或者控制器的返回值
- const result = await next();
- // 控制器之后执行的逻辑
- // 返回给上一个中间件的结果
- return result;
- };
- }
- }
- ```
|