controller.mdc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. ---
  2. description: 控制器(Controller)
  3. globs:
  4. ---
  5. # 控制器(Controller)
  6. 为了实现`快速CRUD`与`自动路由`功能,框架基于[midwayjs controller](mdc:https:/www.midwayjs.org/docs/controller),进行改造加强
  7. 完全继承[midwayjs controller](mdc:https:/www.midwayjs.org/docs/controller)的所有功能
  8. `快速CRUD`与`自动路由`,大大提高编码效率与编码量
  9. ## 路由前缀
  10. 虽然可以手动设置,但是我们并不推荐,cool-admin 在全局权限校验包含一定的规则,
  11. 如果你没有很了解框架原理手动设置可能产生部分功能失效的问题
  12. ### 手动
  13. `/api/other`
  14. 无通用 CRUD 设置方法
  15. ```ts
  16. import { CoolController, BaseController } from "@cool-midway/core";
  17. /**
  18. * 商品
  19. */
  20. @CoolController("/api")
  21. export class AppDemoGoodsController extends BaseController {
  22. /**
  23. * 其他接口
  24. */
  25. @Get("/other")
  26. async other() {
  27. return this.ok("hello, cool-admin!!!");
  28. }
  29. }
  30. ```
  31. 含通用 CRUD 配置方法
  32. ```ts
  33. import { Get } from "@midwayjs/core";
  34. import { CoolController, BaseController } from "@cool-midway/core";
  35. import { DemoGoodsEntity } from "../../entity/goods";
  36. /**
  37. * 商品
  38. */
  39. @CoolController({
  40. prefix: "/api",
  41. api: ["add", "delete", "update", "info", "list", "page"],
  42. entity: DemoGoodsEntity,
  43. })
  44. export class AppDemoGoodsController extends BaseController {
  45. /**
  46. * 其他接口
  47. */
  48. @Get("/other")
  49. async other() {
  50. return this.ok("hello, cool-admin!!!");
  51. }
  52. }
  53. ```
  54. ### 自动
  55. 大多数情况下你无需指定自己的路由前缀,路由前缀将根据规则自动生成。
  56. ::: warning 警告
  57. 自动路由只影响模块中的 controller,其他位置建议不要使用
  58. :::
  59. `src/modules/demo/controller/app/goods.ts`
  60. 路由前缀是根据文件目录文件名按照[规则](mdc:src/guide/core/controller.html#规则)生成的,上述示例生成的路由为
  61. `http://127.0.0.1:8001/app/demo/goods/xxx`
  62. `xxx`代表具体的方法,如: `add`、`page`、`other`
  63. ```ts
  64. import { Get } from "@midwayjs/core";
  65. import { CoolController, BaseController } from "@cool-midway/core";
  66. import { DemoGoodsEntity } from "../../entity/goods";
  67. /**
  68. * 商品
  69. */
  70. @CoolController({
  71. api: ["add", "delete", "update", "info", "list", "page"],
  72. entity: DemoGoodsEntity,
  73. })
  74. export class AppDemoGoodsController extends BaseController {
  75. /**
  76. * 其他接口
  77. */
  78. @Get("/other")
  79. async other() {
  80. return this.ok("hello, cool-admin!!!");
  81. }
  82. }
  83. ```
  84. ### 规则
  85. /controller 文件夹下的文件夹名或者文件名/模块文件夹名/方法名
  86. #### 举例
  87. ```ts
  88. // 模块目录
  89. ├── modules
  90. │ └── demo(模块名)
  91. │ │ └── controller(api接口)
  92. │ │ │ └── app(参数校验)
  93. │ │ │ │ └── goods.ts(商品的controller)
  94. │ │ │ └── pay.ts(支付的controller)
  95. │ │ └── config.ts(必须,模块的配置)
  96. │ │ └── init.sql(可选,初始化该模块的sql)
  97. ```
  98. 生成的路由前缀为:
  99. `/pay/demo/xxx(具体的方法)`与`/app/demo/goods/xxx(具体的方法)`
  100. ## CRUD
  101. ### 参数配置(CurdOption)
  102. 通用增删改查配置参数
  103. | 参数 | 类型 | 说明 | 备注 |
  104. | ------------------ | -------- | ------------------------------------------------------------- | ---- |
  105. | prefix | String | 手动设置路由前缀 | |
  106. | api | Array | 快速 API 接口可选`add` `delete` `update` `info` `list` `page` | |
  107. | serviceApis | Array | 将 service 方法注册为 api,通过 post 请求,直接调用 service 方法 | |
  108. | pageQueryOp | QueryOp | 分页查询设置 | |
  109. | listQueryOp | QueryOp | 列表查询设置 | |
  110. | insertParam | Function | 请求插入参数,如新增的时候需要插入当前登录用户的 ID | |
  111. | infoIgnoreProperty | Array | `info`接口忽略返回的参数,如用户信息不想返回密码 | |
  112. ### 查询配置(QueryOp)
  113. 分页查询与列表查询配置参数
  114. | 参数 | 类型 | 说明 | 备注 |
  115. | ----------------- | -------- | ----------------------------------------------------------------------------------- | ---- |
  116. | keyWordLikeFields | Array | 支持模糊查询的字段,如一个表中的`name`字段需要模糊查询 | |
  117. | where | Function | 其他查询条件 | |
  118. | select | Array | 选择查询字段 | |
  119. | fieldEq | Array | 筛选字段,字符串数组或者对象数组{ column: string, requestParam: string },如 type=1 | |
  120. | fieldLike | Array | 模糊查询字段,字符串数组或者对象数组{ column: string, requestParam: string },如 title | |
  121. | addOrderBy | Object | 排序 | |
  122. | join | JoinOp[] | 关联表查询 | |
  123. ### 关联表(JoinOp)
  124. 关联表查询配置参数
  125. | 参数 | 类型 | 说明 |
  126. | --------- | ------ | ------------------------------------------------------------------ |
  127. | entity | Class | 实体类,注意不能写表名 |
  128. | alias | String | 别名,如果有关联表默认主表的别名为`a`, 其他表一般按 b、c、d...设置 |
  129. | condition | String | 关联条件 |
  130. | type | String | 内关联: 'innerJoin', 左关联:'leftJoin' |
  131. ### 完整示例
  132. ```ts
  133. import { Get } from "@midwayjs/core";
  134. import { CoolController, BaseController } from "@cool-midway/core";
  135. import { BaseSysUserEntity } from "../../../base/entity/sys/user";
  136. import { DemoAppGoodsEntity } from "../../entity/goods";
  137. /**
  138. * 商品
  139. */
  140. @CoolController({
  141. // 添加通用CRUD接口
  142. api: ["add", "delete", "update", "info", "list", "page"],
  143. // 8.x新增,将service方法注册为api,通过post请求,直接调用service方法
  144. serviceApis: [
  145. 'use',
  146. {
  147. method: 'test1',
  148. summary: '不使用多租户', // 接口描述
  149. },
  150. 'test2', // 也可以不设置summary
  151. ]
  152. // 设置表实体
  153. entity: DemoAppGoodsEntity,
  154. // 向表插入当前登录用户ID
  155. insertParam: (ctx) => {
  156. return {
  157. // 获得当前登录的后台用户ID,需要请求头传Authorization参数
  158. userId: ctx.admin.userId,
  159. };
  160. },
  161. // 操作crud之前做的事情 @cool-midway/core@3.2.14 新增
  162. before: (ctx) => {
  163. // 将前端的数据转JSON格式存数据库
  164. const { data } = ctx.request.body;
  165. ctx.request.body.data = JSON.stringify(data);
  166. },
  167. // info接口忽略价格字段
  168. infoIgnoreProperty: ["price"],
  169. // 分页查询配置
  170. pageQueryOp: {
  171. // 让title字段支持模糊查询
  172. keyWordLikeFields: ["title"],
  173. // 让type字段支持筛选,请求筛选字段与表字段一致是情况
  174. fieldEq: ["type"],
  175. // 多表关联,请求筛选字段与表字段不一致的情况
  176. fieldEq: [{ column: "a.id", requestParam: "id" }],
  177. // 让title字段支持模糊查询,请求参数为title
  178. fieldLike: ['a.title'],
  179. // 让title字段支持模糊查询,请求筛选字段与表字段不一致的情况
  180. fieldLike: [{ column: "a.title", requestParam: "title" }],
  181. // 指定返回字段,注意多表查询这个是必要的,否则会出现重复字段的问题
  182. select: ["a.*", "b.name", "a.name AS userName"],
  183. // 4.x置为过时 改用 join 关联表用户表
  184. leftJoin: [
  185. {
  186. entity: BaseSysUserEntity,
  187. alias: "b",
  188. condition: "a.userId = b.id",
  189. },
  190. ],
  191. // 4.x新增
  192. join: [
  193. {
  194. entity: BaseSysUserEntity,
  195. alias: "b",
  196. condition: "a.userId = b.id",
  197. type: "innerJoin",
  198. },
  199. ],
  200. // 4.x 新增 追加其他条件
  201. extend: async (find: SelectQueryBuilder<DemoGoodsEntity>) => {
  202. find.groupBy("a.id");
  203. },
  204. // 增加其他条件
  205. where: async (ctx) => {
  206. // 获取body参数
  207. const { a } = ctx.request.body;
  208. return [
  209. // 价格大于90
  210. ["a.price > :price", { price: 90.0 }],
  211. // 满足条件才会执行
  212. ["a.price > :price", { price: 90.0 }, "条件"],
  213. // 多个条件一起
  214. [
  215. "(a.price = :price or a.userId = :userId)",
  216. { price: 90.0, userId: ctx.admin.userId },
  217. ],
  218. ];
  219. },
  220. // 添加排序
  221. addOrderBy: {
  222. price: "desc",
  223. },
  224. },
  225. })
  226. export class DemoAppGoodsController extends BaseController {
  227. /**
  228. * 其他接口
  229. */
  230. @Get("/other")
  231. async other() {
  232. return this.ok("hello, cool-admin!!!");
  233. }
  234. }
  235. ```
  236. ::: warning
  237. 如果是多表查询,必须设置 select 参数,否则会出现重复字段的错误,因为每个表都继承了 BaseEntity,至少都有 id、createTime、updateTime 三个相同的字段。
  238. :::
  239. 通过这一波操作之后,我们的商品接口的功能已经很强大了,除了通用的 CRUD,我们的接口还支持多种方式的数据筛选
  240. ### 获得 ctx 对象
  241. ```ts
  242. @CoolController(
  243. {
  244. api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  245. entity: DemoAppGoodsEntity,
  246. // 获得ctx对象
  247. listQueryOp: ctx => {
  248. return new Promise<QueryOp>(res => {
  249. res({
  250. fieldEq: [],
  251. });
  252. });
  253. },
  254. // 获得ctx对象
  255. pageQueryOp: ctx => {
  256. return new Promise<QueryOp>(res => {
  257. res({
  258. fieldEq: [],
  259. });
  260. });
  261. },
  262. },
  263. {
  264. middleware: [],
  265. }
  266. )
  267. ```
  268. ### 接口调用
  269. `add` `delete` `update` `info` 等接口可以用法[参照快速开始](mdc:src/guide/quick.html#接口调用)
  270. 这里详细说明下`page` `list`两个接口的调用方式,这两个接口调用方式差不多,一个是分页一个是非分页。
  271. 以`page`接口为例
  272. #### 分页
  273. POST `/admin/demo/goods/page` 分页数据
  274. **请求**
  275. Url: http://127.0.0.1:8001/admin/demo/goods/page
  276. Method: POST
  277. #### Body
  278. ```json
  279. {
  280. "keyWord": "商品标题", // 模糊搜索,搜索的字段对应keyWordLikeFields
  281. "type": 1, // 全等于筛选,对应fieldEq
  282. "page": 2, // 第几页
  283. "size": 1, // 每页返回个数
  284. "sort": "desc", // 排序方向
  285. "order": "id" // 排序字段
  286. }
  287. ```
  288. **返回**
  289. ```json
  290. {
  291. "code": 1000,
  292. "message": "success",
  293. "data": {
  294. "list": [
  295. {
  296. "id": 4,
  297. "createTime": "2021-03-12 16:23:46",
  298. "updateTime": "2021-03-12 16:23:46",
  299. "title": "这是一个商品2",
  300. "pic": "https://show.cool-admin.com/uploads/20210311/2e393000-8226-11eb-abcf-fd7ae6caeb70.png",
  301. "price": "99.00",
  302. "userId": 1,
  303. "type": 1,
  304. "name": "超级管理员"
  305. }
  306. ],
  307. "pagination": {
  308. "page": 2,
  309. "size": 1,
  310. "total": 4
  311. }
  312. }
  313. }
  314. ```
  315. ### 服务注册成 Api
  316. 很多情况下,我们在`Controller`层并不想过多地操作,而是想直接调用`Service`层的方法,这个时候我们可以将`Service`层的方法注册成`Api`,那么你的某个`Service`方法就变成了`Api`。
  317. #### 示例:
  318. 在 Controller 中
  319. ```ts
  320. import { CoolController, BaseController } from "@cool-midway/core";
  321. import { DemoGoodsEntity } from "../../entity/goods";
  322. import { DemoTenantService } from "../../service/tenant";
  323. /**
  324. * 示例
  325. */
  326. @CoolController({
  327. serviceApis: [
  328. "use",
  329. {
  330. method: "test1",
  331. summary: "不使用多租户", // 接口描述
  332. },
  333. "test2", // 也可以不设置summary
  334. ],
  335. entity: DemoGoodsEntity,
  336. service: DemoXxxService,
  337. })
  338. export class AdminDemoTenantController extends BaseController {}
  339. ```
  340. 在 Service 中
  341. ```ts
  342. /**
  343. * 示例服务
  344. */
  345. @Provide()
  346. export class DemoXxxService extends BaseService {
  347. /**
  348. * 示例方法1
  349. */
  350. async test1(params) {
  351. console.log(params);
  352. return "test1";
  353. }
  354. /**
  355. * 示例方法2
  356. */
  357. async test2() {
  358. return "test2";
  359. }
  360. }
  361. ```
  362. ::: warning 注意
  363. `serviceApis` 注册为`Api`的请求方法是`POST`,所以`Service`层的方法参数需要通过`body`传递
  364. :::
  365. ### 重写 CRUD 实现
  366. 在实际开发过程中,除了这些通用的接口可以满足大部分的需求,但是也有一些特殊的需求无法满足用户要求,这个时候也可以重写`add` `delete` `update` `info` `list` `page` 的实现
  367. #### 编写 service
  368. 在模块新建 service 文件夹(名称非强制性),再新建一个`service`实现,继承框架的`BaseService`
  369. ```ts
  370. import { Inject, Provide } from "@midwayjs/core";
  371. import { BaseService } from "@cool-midway/core";
  372. import { InjectEntityModel } from "@midwayjs/orm";
  373. import { Repository } from "typeorm";
  374. import { BaseSysMenuEntity } from "../../entity/sys/menu";
  375. import * as _ from "lodash";
  376. import { BaseSysPermsService } from "./perms";
  377. /**
  378. * 菜单
  379. */
  380. @Provide()
  381. export class BaseSysMenuService extends BaseService {
  382. @Inject()
  383. ctx;
  384. @InjectEntityModel(BaseSysMenuEntity)
  385. baseSysMenuEntity: Repository<BaseSysMenuEntity>;
  386. @Inject()
  387. baseSysPermsService: BaseSysPermsService;
  388. /**
  389. * 重写list实现
  390. */
  391. async list() {
  392. const menus = await this.getMenus(
  393. this.ctx.admin.roleIds,
  394. this.ctx.admin.username === "admin"
  395. );
  396. if (!_.isEmpty(menus)) {
  397. menus.forEach((e) => {
  398. const parentMenu = menus.filter((m) => {
  399. e.parentId = parseInt(e.parentId);
  400. if (e.parentId == m.id) {
  401. return m.name;
  402. }
  403. });
  404. if (!_.isEmpty(parentMenu)) {
  405. e.parentName = parentMenu[0].name;
  406. }
  407. });
  408. }
  409. return menus;
  410. }
  411. }
  412. ```
  413. #### 设置服务实现
  414. `CoolController`设置自己的服务实现
  415. ```ts
  416. import { Inject } from "@midwayjs/core";
  417. import { CoolController, BaseController } from "@cool-midway/core";
  418. import { BaseSysMenuEntity } from "../../../entity/sys/menu";
  419. import { BaseSysMenuService } from "../../../service/sys/menu";
  420. /**
  421. * 菜单
  422. */
  423. @CoolController({
  424. api: ["add", "delete", "update", "info", "list", "page"],
  425. entity: BaseSysMenuEntity,
  426. service: BaseSysMenuService,
  427. })
  428. export class BaseSysMenuController extends BaseController {
  429. @Inject()
  430. baseSysMenuService: BaseSysMenuService;
  431. }
  432. ```
  433. ## 路由标签
  434. 我们经常有这样的需求:给某个请求地址打上标记,如忽略 token,忽略签名等。
  435. ```ts
  436. import { Get, Inject } from "@midwayjs/core";
  437. import {
  438. CoolController,
  439. BaseController,
  440. CoolUrlTag,
  441. TagTypes,
  442. CoolUrlTagData,
  443. } from "@cool-midway/core";
  444. /**
  445. * 测试给URL打标签
  446. */
  447. @CoolController({
  448. api: [],
  449. entity: "",
  450. pageQueryOp: () => {},
  451. })
  452. // add 接口忽略token
  453. @CoolUrlTag({
  454. key: TagTypes.IGNORE_TOKEN,
  455. value: ["add"],
  456. })
  457. export class DemoAppTagController extends BaseController {
  458. @Inject()
  459. tag: CoolUrlTagData;
  460. /**
  461. * 获得标签数据, 如可以标记忽略token的url,然后在中间件判断
  462. * @returns
  463. */
  464. // 这是6.x支持的,可以直接标记这个接口忽略token,更加灵活优雅,但是记得配合@CoolUrlTag()一起使用,也就是Controller上要有这个注解,@CoolTag才会生效
  465. @CoolTag(TagTypes.IGNORE_TOKEN)
  466. @Get("/data")
  467. async data() {
  468. return this.ok(this.tag.byKey(TagTypes.IGNORE_TOKEN));
  469. }
  470. }
  471. ```
  472. #### 中间件
  473. ```ts
  474. import { CoolUrlTagData, TagTypes } from "@cool-midway/core";
  475. import { IMiddleware } from "@midwayjs/core";
  476. import { Inject, Middleware } from "@midwayjs/core";
  477. import { NextFunction, Context } from "@midwayjs/koa";
  478. @Middleware()
  479. export class DemoMiddleware implements IMiddleware<Context, NextFunction> {
  480. @Inject()
  481. tag: CoolUrlTagData;
  482. resolve() {
  483. return async (ctx: Context, next: NextFunction) => {
  484. const urls = this.tag.byKey(TagTypes.IGNORE_TOKEN);
  485. console.log("忽略token的URL数组", urls);
  486. // 这里可以拿到下一个中间件或者控制器的返回值
  487. const result = await next();
  488. // 控制器之后执行的逻辑
  489. // 返回给上一个中间件的结果
  490. return result;
  491. };
  492. }
  493. }
  494. ```