控制器
所有的 controller 都继承自 BaseController,该父类已经实现了以下六个基础方法:add、delete、update、page、list、info,可以大大简化代码,使得 controller 更加简洁。
示例
@Tag(name = "测试CURD", description = "测试CURD")
@CoolRestController(value = "/cool", api = {"add", "delete", "update", "page", "list", "info"})
public class AdminDemoInfoController extends BaseController<DemoService, DemoEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
// 可以在这边实现一下列表数据排序规则,查询条件过滤,返回哪些字段等
}
}
这样就默认实现了以下六个接口:
- POST 新增
- POST 删除
- POST 修改
- POST 分页查询
- POST 查询列表
- GET 根据列表查询单个信息 等 6 个接口
查询配置
@Tag(name = "测试CURD", description = "测试CURD")
@CoolRestController(value = "/cool", api = {"add", "delete", "update", "page", "list", "info"})
public class AdminDemoInfoController extends BaseController<DemoService, DemoEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp() // 分页查询配置
.fieldEq("status") // 字段全匹配,对应请求参数中同名参数
.keyWordLikeFields("name", "phone") // 需要模糊查询的字段,对应请求参数中的 keyWord
.select("name", "phone", "age") // 返回字段
.queryWrapper(QueryWrapper.create())); // 其他查询方式 具体看 https://mybatis-flex.com/zh/base/querywrapper.html文档
setListOption(createOp()); // 列表查询 跟分页查询一样
}
}
以上是针对Entity单表查询方式,如果我们有需要连表查询,查询另一张表的字段,这样就需要在Entity加上另一张表的的字段,针对这种场景,我们可以使用 自定义的查询方式
public class AppMarketCouponUserController extends BaseController<MarketCouponUserService, MarketCouponUserEntity> {
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(
createOp()
.select(MARKET_COUPON_INFO_ENTITY.ALL_COLUMNS,
MARKET_COUPON_USER_ENTITY.STATUS.as("useStatus")) // 查询连表字段
.queryWrapper(
QueryWrapper.create()
.from(MARKET_COUPON_USER_ENTITY)
.leftJoin(MARKET_COUPON_INFO_ENTITY) // leftJoin 连表查询
.on(MARKET_COUPON_USER_ENTITY.USER_ID.eq(
CoolSecurityUtil.getCurrentUserId())
.and(MARKET_COUPON_USER_ENTITY.COUPON_ID.eq(
MARKET_COUPON_INFO_ENTITY.ID))))
.queryModeEnum(QueryModeEnum.CUSTOM) // 自定义查询模式,默认为ENTITY 实体,自定义场景主要使用在关联表查询插件,
// 需要返回的字段分布在各个表里,需要组装数据,通过这种方式,就不需要在entity 中加非存储数据库的字段;默认转成map,注意如果该表有json字段,
// 需要通过下面的 transform 手动进行转换
.asType(Map.class) // 默认为Map,也可以自己定义VO
.transform(o -> {
Map map = (Map) o; // 没有设置asType ,自定义类型默认为 map
if (ObjUtil.isNotEmpty(map.get("condition"))) {
map.put("condition",
JSONUtil.toBean(map.get("condition").toString(), Map.class));
}
})); // 其他查询方式 具体看 https://mybatis-flex.com/zh/base/querywrapper.html文档
}
}
查询模式queryModeEnum 还有 ENTITY_WITH_RELATIONS, 实体中 使用了关联查询 @RelationOneToMany 如下面这个订单查询,订单产品列表也一并查出
关联查询用法参考:https://mybatis-flex.com/zh/base/relations-query.html
@Getter
@Setter
@Table(value = "order_info", comment = "订单信息")
public class OrderInfoEntity extends BaseEntity<OrderInfoEntity> {
@Index
@ColumnDefine(comment = "用户ID", notNull = true)
private Long userId;
// ......
// 订单商品列表
@Ignore
@Column(ignore = true)
@RelationOneToMany(selfField = "id", targetField = "orderId")
private List<OrderGoodsEntity> goodsList;
}
请求参数
为了方便获取参数,框架封装了一个请求属性,将 URL、表单、body
等参数封装在了一个 JSONObject requestParams,开发者可以从中方便的获取各种参数;
@Tag(name = "测试CURD", description = "测试CURD")
@CoolRestController(value = "/cool", api = {"add", "delete", "update", "page", "list", "info"})
public class AdminDemoInfoController extends BaseController<DemoService, DemoEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
// 可以在这边实现一下列表数据排序规则,查询条件过滤,返回哪些字段等
}
@PostMapping("/test")
public R test(@RequestAttribute JSONObject requestparams, String user) {
System.out.println(requestparams.getStr("user"));
return R.ok();
}
}
重写实现
默认实现了通用的六个接口方法,如果不满足需求,可以在对应的 service 中重写方法。
@Tag(name = "测试CURD", description = "测试CURD")
@CoolRestController(value = "/cool", api = {"add", "delete", "update", "page", "list", "info"})
public class AdminDemoInfoController extends BaseController<DemoService, DemoEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
// 可以在这边实现一下列表数据排序规则,查询条件过滤,返回哪些字段等
}
@Override
@PostMapping("/page")
protected R page(JSONObject requestParams, CrudOption<DemoEntity> option) {
// 重写逻辑
return super.page(requestParams, option);
}
}
同样也可以对 service 进行重写
@Service
public class DemoServiceImpl extends BaseServiceImpl<DemoMapper, DemoEntity> implements DemoService {
@Override
public Object page(JSONObject requestParams, Page<DemoEntity> page, QueryWrapper<DemoEntity> queryWrapper) {
// 重写逻辑
return super.page(requestParams, page, queryWrapper);
}
}
和传统的 controller 对比一下是简化了不少,接下来我们看看实现的原理
原理
接口路由规则
// 模块目录
├── modules
│ └── demo(模块名)
│ │ └── controller(接口)
│ │ │ └── admin(后端接口)
│ │ │ │ └── AdminDemoInfoController.java
│ │ │ └── app(前端接口)
│ │ │ │ └── AppDemoInfoController.java
- 生成的路由前缀为: /admin/demo/info/xxx 与/app/demo/info/xxx
- 规则为: admin(app)/模块名/AdminDemoInfoController 转成小写替换掉 admin(app)、模块名和 controller 如果在 AdminDemoInfoController.java 同级下有个 AdminDemoUserInfoController.java文件
该接口解析的前缀则是 /admin/demo/user/info/xxx
注意
这个解析规则在 v7.1.3(2024-08-25) 版本才有,在历史版本中需在 admin 包下加一级 user 包,否则这个controller eps识别不了
自定义路由注解
public @interface CoolRestController {
@AliasFor(annotation = RequestMapping.class)
String name() default "";
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
String[] api() default {};
}
重写 getMappingForMethod
我们不直接使用 spring 自带的注解,而是对它做一层封装,controller 上加了这个注解后
将由以下这个类实现自动配置模块路由,该类继承了 spring 的 RequestMappingHandlerMapping,并重写了 getMappingForMethod 方法
根据一定规则生成 path,返回给 spring 做解析。
public class AutoPrefixUrlMapping extends RequestMappingHandlerMapping {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
CoolRestController[] annotations = handlerType.getAnnotationsByType(CoolRestController.class);
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
// ... 按规则组装 path
info = info.mutate().paths(path).build().combine(info);
return info;
}
}