# 后端
本章节讲述如何在搭好的后端脚手架上,简单地开发增删改查功能。
# 生成代码
在脚手架资源下载“后端代码生成器”。用编辑器打开代码生成器项目。代码生成器可以为我们根据数据库表结构,预初始化好“实体类”、“持久层”、“业务逻辑层”、“Controller层”,能有效提高我们的效率。
- 打开Generator.java
- 修改一下代码
String projectPath = "填写项目根目录路径"; // 例:F:\代码库\code-deep-Git\hhh-megrez-bridge
gc.setAuthor("作者名称");
dsc.setUrl("数据库地址"); // 例:jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&useSSL=false
dsc.setDriverName("数据库驱动"); // 例:com.mysql.cj.jdbc.Driver
dsc.setUsername("数据库账号");
dsc.setPassword("数据库密码");
pc.setParent("com.hhh.cloud.framework.项目名"); // 例:假设项目叫“监管系统(supervise-os)”,项目名可以叫“supervise”,则填写“com.hhh.cloud.framework.supervise”
- 运行Generator.java,根据提示依次填写生成的代码块所属模块名、数据表名。
- 查看项目,会发现项目中多了一个模块,模块下有controller(Controller层)、service(业务逻辑层)、mapper(java持久层)、entity(实体类),里面分别有相关的代码。
- 再查看/src/main/resources/mapper,会发现同样生成一个模块,并生成了***Mapper.xml,这也是持久层,是mybatis的数据库操作脚本,复杂的自定义sql脚本都要写在这里。
# Controller开发
# 出参入参
和前端脚手架的业务交互数据,有固定的出入参规范。
- 要求交互使用http的“application/json”请求,入参出参自动转为json格式。
- 入参使用JavaBean接收,使用@RequestBody注解获取,如:
@RequestBody CdCredential credential
- 出参使用“com.hhh.cloud.framework.util.result.Result”和“com.hhh.cloud.framework.util.result.TableResult”。
# 入参校验
- 使用javabean接收入参,要校验的bean必须加注解@Valid。
public Result monitorData(@RequestBody @Valid MonitorClientData clientData) {}
- 接收入参的bean中,增加校验注解。
@NotBlank(message = "发送端编号不能为空")
@Size(max = 32,message = "发送端编号超过最大长度限制")
private String clientCode;
- 常用的校验注解
| 注解 | 类型 | 说明 |
|---|---|---|
| @NotNull | 任何类型 | 属性不能为null |
| @NotEmpty | 集合 | 集合不能为null,且size大于0 |
| @NotBlanck | 字符串、字符 | 字符类不能为null,且去掉空格之后长度大于0 |
| @AssertTrue | Boolean、boolean | 布尔属性必须是true |
| @Min | 数字类型(原子和包装) | 限定数字的最小值(整型) |
| @Max | 同@Min | 限定数字的最大值(整型) |
| @DecimalMin | 同@Min | 限定数字的最小值(字符串,可以是小数) |
| @DecimalMax | 同@Min | 限定数字的最大值(字符串,可以是小数) |
| @Range | 数字类型(原子和包装) | 限定数字范围(长整型) |
| @Length | 字符串 | 限定字符串长度 |
| @Size | 集合 | 限定集合大小 |
| @Past | 时间、日期 | 必须是一个过去的时间或日期 |
| @Future | 时期、时间 | 必须是一个未来的时间或日期 |
| 字符串 | 必须是一个邮箱格式 | |
| @Pattern | 字符串、字符 | 正则匹配字符串 |
# CRUD接口
- 打开模块的Controller,把类上的“@Controller”改为“@RestController”。前后端分离不需要视图层,所以就不需要用“@Controller”了。
- CRUD接口分为:新增、列表查询、详情查询、编辑、删除。具体代码参考:
/**
* 新增
*/
@PostMapping("add")
public Result<Boolean> addCredential(@RequestBody CdCredential credential) {
if(cdCredentialService.count(new LambdaQueryWrapper<CdCredential>().eq(CdCredential::getName, credential.getName())) > 0) {
return new Result<>(CodeEnum.BUSINESS_ERROR.get(), false, "证书名称已存在");
}
credential.setId(CommonUtil.GUID());
UserSet.create(UserContextHolder.get().getUserJwt(), credential);
cdCredentialService.save(credential);
return new Result<>(CodeEnum.SUCCESS.get(), true, "");
}
/**
* 详情查询
*/
@PostMapping("get")
public Result<CdCredential> getCredential(@RequestBody CdCredential credential) {
return new Result<>(CodeEnum.SUCCESS.get(), cdCredentialService.getById(credential.getId()), "");
}
/**
* 列表查询
*/
@PostMapping("list")
public TableResult<List<CdCredential>> listCredential(@RequestBody CdCredentialSearch search) {
IPage<CdCredential> page = cdCredentialService.listCredential(search);
return new TableResult<>(CodeEnum.SUCCESS.get(), page.getRecords(), page.getTotal(), "");
}
/**
* 编辑
*/
@PostMapping("edit")
public Result<Boolean> editCredential(@RequestBody CdCredential credential) {
UserSet.update(UserContextHolder.get().getUserJwt(), credential);
cdCredentialService.updateById(credential);
return new Result<>(CodeEnum.SUCCESS.get(), true, "");
}
/**
* 删除
*/
@PostMapping("del")
public Result<Boolean> delCredential(@RequestBody CdCredential credential) {
cdCredentialService.removeById(credential.getId());
return new Result<>(CodeEnum.SUCCESS.get(), true, "");
}
# service层开发
使用代码生成器,service层会继承“ServiceImpl”类。该类实现了单表的增删改查方法,如保存数据,直接调用save()即可,不需要开发者动手实现。单表查询也可以使用QueryWrapper进行,有条件更新数据的,也可以使用UpdateWrapper。具体可以阅读mybatis-plus官方文档 (opens new window)
# 出参入参
出入参必须准确。
应该严格要求入参,只接收对业务逻辑有用的入参,达到方法可以复用。
出参准确,尽量不要直接返回Result或TableResult。封装返回给前端的逻辑应该是Controller应该做的事。
# 调用持久层
ServiceImpl默认引用对应的实体类Mapper,如果service层需要使用mapper,可以直接使用baseMapper调用方法。
baseMapper.selectCount(new LambdaQueryWrapper<CdCredential>().eq(CdCredential::getName, "123"));
# bean引用
非必要尽量不要引用其他业务的service层,避免循环依赖。如果需要查询其他实体类的数据,可以直接引用Mapper。
@Autowired
private CdProjectPropertyFileModelMapper cdProjectPropertyFileModelMapper;
# 列表查询
列表查询相对复发,需要编写,例:
@Override
public IPage<CdCredential> listCredential(CdCredentialSearch search) {
LambdaQueryWrapper<CdCredential> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(search.getName()), CdCredential::getName, QuerySet.likeParam(search.getName()));
queryWrapper.like(StringUtils.isNotBlank(search.getUsername()), CdCredential::getUsername, QuerySet.likeParam(search.getUsername()));
return page(new Page<>(search.getPage(), search.getLimit()), queryWrapper);
}
# 持久层开发
# 自定义sql
这部分可以参考mybatis文档 (opens new window)
复杂的查询:
- 在Mapper.java中添加接口
public interface CdEnvProjectMapper extends BaseMapper<CdEnvProject> {
public Page<CdEnvProjectSearch> list(Page<CdEnvProjectSearch> resultPage, @Param("map") CdEnvProjectSearch search);
}
- 在对应的Mapper.xml中增加脚本
<select id="list" resultType="com.hhh.cloud.framework.code.env.model.CdEnvProjectSearch">
SELECT
cep.id,
cep.env_id,
cep.project_id,
cep.unit_id,
cep.unit_name,
cep.management_id,
cep.management_name,
cep.creator_id,
cep.creator_name,
cep.create_time,
cep.remark,
cep.disable,
cp.project_code,
cp.project_name
FROM cd_env_project cep
LEFT JOIN cd_project cp ON cep.project_id=cp.id WHERE cep.env_id=#{map.envId}
<if test="map.groupId != null and map.groupId != ''">
and cp.group_id=#{map.groupId}
</if>
<if test="map.projectName != null and map.projectName != ''">
and cp.project_name=#{map.projectName}
</if>
AND cep.invalid=0
ORDER BY cep.create_time DESC
</select>
# 逻辑删除
脚手架中的配置文件已经有配置逻辑删除
#逻辑删除配置
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
打开有逻辑删除的实体类,在逻辑删除字段上增加注解“@TableLogic”。
/**
* 逻辑删除(0未删除,1已删除)
*/
@TableLogic
private Integer invalid;
如果自定义sql,需要在sql上自己加上逻辑删除条件。
AND invalid=0
# 列表查询
- 在Mapper.java中添加接口
public interface CdEnvProjectMapper extends BaseMapper<CdEnvProject> {
public Page<CdEnvProjectSearch> list(Page<CdEnvProjectSearch> resultPage, @Param("map") CdEnvProjectSearch search);
}
- 在对应的Mapper.xml中增加脚本
<select id="list" resultType="com.hhh.cloud.framework.code.env.model.CdEnvProjectSearch">
SELECT
cep.id,
cep.env_id,
cep.project_id,
cep.unit_id,
cep.unit_name,
cep.management_id,
cep.management_name,
cep.creator_id,
cep.creator_name,
cep.create_time,
cep.remark,
cep.disable,
cp.project_code,
cp.project_name
FROM cd_env_project cep
LEFT JOIN cd_project cp ON cep.project_id=cp.id WHERE cep.env_id=#{map.envId}
<if test="map.groupId != null and map.groupId != ''">
and cp.group_id=#{map.groupId}
</if>
<if test="map.projectName != null and map.projectName != ''">
and cp.project_name=#{map.projectName}
</if>
AND cep.invalid=0
ORDER BY cep.create_time DESC
</select>
# 权限处理
可以使用工具类中的“ClassFilterUtil”设置权限参数
/**
* 继承参数数据过滤工具类
* @param params 过滤条件
*/
public static void getClssParams(Map<String,Object> params, String requestURI);
/**
* QueryWrapper封装过滤权限
* @param queryWrapper 查询条件
* @param params 入参(包含已经使用getClssParams返回的入参)
*/
public static void initClassQuery(QueryWrapper queryWrapper, Map<String,Object> params);
自定义sql上,增加以下查询条件:
<sql id="classFilter">
<if test="unitId != null and unitId != '' ">
And D.unit_id = #{unitId}
</if>
<choose>
<when test="ALL != null"></when>
<when test="UNIT_MORE != null">
<if test="UNIT_MORE != null">
And D.unit_id in
<foreach collection="UNIT_MORE" item="unitId" index="index" open="(" close=")" separator=",">
#{unitId}
</foreach>
</if>
</when>
<when test="GROUP != null">
<if test="GROUP != null">
And D.unit_id in
<foreach collection="GROUP" item="unitId" index="index" open="(" close=")" separator=",">
#{unitId}
</foreach>
</if>
</when>
<when test="DEPT_MORE != null">
<if test="DEPT_MORE != null">
And D.management_id in
<foreach collection="DEPT_MORE" item="managementId" index="index" open="(" close=")" separator=",">
#{managementId}
</foreach>
</if>
</when>
<when test="DEPT_SUB != null">
<if test="DEPT_SUB != null">
And D.management_id in
<foreach collection="DEPT_SUB" item="managementId" index="index" open="(" close=")" separator=",">
#{managementId}
</foreach>
</if>
</when>
<when test="DEPT != null">
<if test="DEPT != null">
And D.management_id in
<foreach collection="DEPT" item="managementId" index="index" open="(" close=")" separator=",">
#{managementId}
</foreach>
</if>
</when>
<when test="PERSON != null">
<if test="PERSON != null">
And D.creator_id =
<foreach collection="PERSON" item="creatorId" index="index" >
#{creatorId}
</foreach>
</if>
</when>
</choose>
</sql>