JP3-3-MyClub后台后端(二)
Java道经 - 项目 - MyClub - 后台后端(二)
传送门:JP3-1-MyClub项目简介
传送门:JP3-2-MyClub公共服务
传送门:JP3-3-MyClub后台后端(一)
传送门:JP3-3-MyClub后台后端(二)
文章目录
- S03. UMS用户模块
- E01. 开发部门接口
- 1. 基础增删改查
- 2. 下载数据报表
- E02. 开发员工接口
- 1. 基础增删改查
- 2. 下载数据报表
- 3. 上传员工头像
- 4. 修改登录密码
- 5. 账号密码登录
- E03. 开发角色接口
- 1. 基础增删改查
- 2. 下载数据报表
- 3. 查询员工角色
- 4. 修改员工角色
- E04. 开发菜单接口
- 1. 基础增删改查
- 2. 下载数据报表
- 3. 查询角色菜单
- 4. 修改角色菜单
- 5. 查询员工菜单
S03. UMS用户模块
E01. 开发部门接口
心法:部门记录需要关联房间记录,所以需要事先对实体类进行改造。
改造如下:
package com.joezhou.entity;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {.../** 每条部门记录对应 1 条房间记录 */private Room room;
}
1. 基础增删改查
- 开发 DTO 实体类:
负责(添加)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "部门添加DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptInsertDTO implements Serializable {@Schema(description = "部门名称")@NotEmpty(message = "部门名称不能为空")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "部门描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;@Schema(description = "房间外键")@NotNull(message = "房间外键不能为空")private Long fkRoomId;
}
负责(修改)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "部门修改DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptUpdateDTO implements Serializable {@Schema(description = "主键")@NotNull(message = "主键不能为空")private Long id;@Schema(description = "部门名称")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "部门描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;@Schema(description = "房间外键")private Long fkRoomId;
}
负责(分页)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Schema(description = "按条件分页搜索部门DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptPageDTO extends PageDTO {@Schema(description = "部门名称")private String title;
}
负责(全查)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptVO implements Serializable {/** 主键 */private Long id;/** 部门名称 */private String title;
}
- 开发数据层代码:
package com.joezhou.mapper;/** @author 周航宇 */
@Repository
public interface DeptMapper {@Insert("""insert into ums_dept (title, info, fk_room_id, version, deleted, created, updated)values (#{title}, #{info}, #{fkRoomId}, #{version}, #{deleted}, #{created}, #{updated})""")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(Dept dept);@Results(id = "deptResultMap", value = {@Result(property = "id", column = "id", id = true),@Result(property = "fkRoomId", column = "fk_room_id"),@Result(property = "room", column = "fk_room_id", one = @One(select = "com.joezhou.mapper.RoomMapper.select")),})@Select("""select * from ums_dept twhere t.id = #{param1} and t.deleted = 0""")Dept select(Long id);@ResultMap("deptResultMap")@Select("""<script>select * from ums_dept t<where><if test='title != null'> title like concat('%', #{title}, '%') and </if>t.deleted = 0</where></script>""")List<Dept> list(DeptPageDTO dto);@Update("""<script>update ums_dept<set><if test='title != null'> title = #{title}, </if><if test='info != null'> info = #{info}, </if><if test='fkRoomId != null'> fk_room_id = #{fkRoomId}, </if><if test='deleted != null'> deleted = #{deleted}, </if><if test='created != null'> created = #{created}, </if><if test='updated != null'> updated = #{updated}, </if>version = version + 1</set>where id = #{id} and deleted = 0 and version = #{version}</script>""")int update(Dept dept);@Update("""update ums_dept set deleted = 1, updated = current_timestampwhere id = #{param1}""")int delete(Long id);@Update("""<script>update ums_dept set deleted = 1, updated = current_timestampwhere id in<foreach collection='list' item='e' open='(' close=')' separator=','>${e}</foreach></script>""")int deleteBatch(List<Long> ids);
}
- 开发业务层代码:
package com.joezhou.service;/** @author 周航宇 */
public interface DeptService {int insert(DeptInsertDTO dto);Dept select(Long id);List<DeptVO> list();PageInfo<Dept> page(DeptPageDTO dto);int update(DeptUpdateDTO dto);int delete(Long id);int deleteBatch(List<Long> ids);
}
package com.joezhou.service.impl;/** @author 周航宇 */
@Service
@CacheConfig(cacheNames = "dept")
public class DeptServiceImpl implements DeptService {@Resourceprivate DeptMapper deptMapper;@CacheEvict(allEntries = true)@Overridepublic int insert(DeptInsertDTO dto) {String info = dto.getInfo();// 拷贝属性Dept dept = BeanUtil.copyProperties(dto, Dept.class);// 设置默认值dept.setInfo(StrUtil.isBlank(info) ? "暂无描述" : info);dept.setVersion(0L);dept.setDeleted(0);dept.setCreated(LocalDateTime.now());dept.setUpdated(LocalDateTime.now());// DB添加int result = deptMapper.insert(dept);if (result <= 0) {throw new ServerErrorException("DB添加失败");}return result;}@Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")@Overridepublic Dept select(Long id) {Dept result = deptMapper.select(id);if (ObjectUtil.isNull(result)) {throw new ServerErrorException("记录不存在");}return result;}@Cacheable(key = "#root.methodName", unless = "#result == null")@Overridepublic List<DeptVO> list() {return deptMapper.list(new DeptPageDTO()).stream().map(dept -> BeanUtil.copyProperties(dept, DeptVO.class)).collect(Collectors.toList());}@Cacheable(key = "#root.methodName + ':' + #p0.toString()", condition = "#p0 != null", unless = "#result == null")@Overridepublic PageInfo<Dept> page(DeptPageDTO dto) {PageHelper.startPage(dto.getPageNum(), dto.getPageSize());return new PageInfo<>(deptMapper.list(dto));}@CacheEvict(allEntries = true)@Transactional@Retryable(retryFor = VersionException.class)@Overridepublic int update(DeptUpdateDTO dto) {Dept dept = deptMapper.select(dto.getId());if (ObjectUtil.isNull(dept)) {throw new ServerErrorException("记录不存在");}BeanUtil.copyProperties(dto, dept);// 设置默认值dept.setUpdated(LocalDateTime.now());// DB修改int result = deptMapper.update(dept);if (result <= 0) {throw new VersionException("DB修改失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int delete(Long id) {int result = deptMapper.delete(id);if (result <= 0) {throw new ServerErrorException("DB逻辑删除失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int deleteBatch(List<Long> ids) {int result = deptMapper.deleteBatch(ids);if (result <= 0) {throw new ServerErrorException("DB逻辑批删失败");}return result;}
}
- 开发控制层代码:
package com.joezhou.controller;/** @author 周航宇 */
@Tag(name = "部门模块")
@RestController
@RequestMapping("/api/v1/dept")
public class DeptController {@Resourceprivate DeptService deptService;@Operation(summary = "新增 - 单条新增")@PostMapping("insert")public Result<Integer> insert(@RequestBody @Validated DeptInsertDTO dto) {return new Result<>(deptService.insert(dto));}@Operation(summary = "查询 - 单条查询")@GetMapping("select/{id}")public Result<Dept> select(@PathVariable("id") Long id) {return new Result<>(deptService.select(id));}@Operation(summary = "查询 - 全部记录")@GetMapping("list")public Result<List<DeptVO>> list() {return new Result<>(deptService.list());}@Operation(summary = "查询 - 分页查询")@GetMapping("page")public Result<PageInfo<Dept>> page(@Validated DeptPageDTO dto) {return new Result<>(deptService.page(dto));}@Operation(summary = "修改 - 单条修改")@PutMapping("update")public Result<Integer> update(@RequestBody @Validated DeptUpdateDTO dto) {return new Result<>(deptService.update(dto));}@Operation(summary = "删除 - 单条删除")@DeleteMapping("delete/{id}")public Result<Integer> delete(@PathVariable("id") Long id) {return new Result<>(deptService.delete(id));}@Operation(summary = "删除 - 批量删除")@DeleteMapping("deleteBatch")public Result<Integer> deleteBatch(@RequestParam("ids") List<Long> ids) {return new Result<>(deptService.deleteBatch(ids));}
}
2. 下载数据报表
- 开发 DTO 实体类:
package com.joezhou.excel;/** @author 周航宇 */
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptExcel implements Serializable {@ExcelProperty(value = {"部门数据统计表", "部门标题"})private String title;@ExcelProperty(value = {"部门数据统计表", "部门描述"})private String info;@ExcelProperty(value = {"部门数据统计表", "所在房间"})private String roomTitle;@ExcelProperty(value = {"部门数据统计表", "首次创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime created;@ExcelProperty(value = {"部门数据统计表", "最后创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime updated;
}
- 开发业务层代码:
package com.joezhou.service;/*** 获取部门记录的Excel数据** @return 部门记录的Excel数据列表*/
List<DeptExcel> getExcelData();
package com.joezhou.service.impl;@Override
public List<DeptExcel> getExcelData() {return deptMapper.list(new DeptPageDTO()).stream().map(dept -> {DeptExcel deptExcel = BeanUtil.copyProperties(dept, DeptExcel.class);if (ObjectUtil.isNotNull(dept.getRoom())) {deptExcel.setRoomTitle(dept.getRoom().getTitle());}return deptExcel;}).collect(Collectors.toList());
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 报表打印")
@SneakyThrows
@GetMapping("/excel")
public void excel(HttpServletResponse resp) {EasyExcelUtil.download(resp, "部门统计表", deptService.getExcelData());
}
E02. 开发员工接口
心法:员工记录需要关联部门记录,所以需要事先对实体类进行改造。
改造如下:
package com.joezhou.entity;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {.../** 每条员工记录对应 1 条部门记录 */private Dept dept;
}
1. 基础增删改查
- 开发 DTO 实体类:
负责(添加)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "员工添加DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpInsertDTO implements Serializable {@Schema(description = "登录账号")@NotEmpty(message = "登录账号不能为空")@Pattern(regexp = MC.Regex.USERNAME_RE, message = MC.Regex.USERNAME_RE_MSG)private String username;@Schema(description = "登录密码")@NotEmpty(message = "登录密码不能为空")@Pattern(regexp = MC.Regex.PASSWORD_RE, message = MC.Regex.PASSWORD_RE_MSG)private String password;@Schema(description = "手机号码")@NotEmpty(message = "手机号码不能为空")@Pattern(regexp = MC.Regex.PHONE_RE, message = MC.Regex.PHONE_RE_MSG)private String phone;@Schema(description = "微信号码")@NotEmpty(message = "微信号码不能为空")private String wechat;@Schema(description = "电子邮箱")@NotEmpty(message = "电子邮箱不能为空")@Pattern(regexp = MC.Regex.EMAIL_RE, message = MC.Regex.EMAIL_RE_MSG)private String email;@Schema(description = "真实姓名")@NotEmpty(message = "真实姓名不能为空")@Pattern(regexp = MC.Regex.REALNAME_RE, message = MC.Regex.REALNAME_RE_MSG)private String realname;@Schema(description = "身份证号")@Pattern(regexp = MC.Regex.ID_CARD_RE, message = MC.Regex.ID_CARD_RE_MSG)@NotEmpty(message = "身份证号不能为空")private String idcard;@Schema(description = "部门外键")@NotNull(message = "部门外键不能为空")private Long fkDeptId;@Schema(description = "员工描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;@Schema(description = "入职日期", example = "2023-10-05T12:12:12Z")@NotNull(message = "入职日期不能为空")private LocalDateTime hiredate;
}
负责(修改)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "员工修改DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpUpdateDTO implements Serializable {@Schema(description = "主键")@NotNull(message = "主键不能为空")private Long id;@Schema(description = "手机号码")@Pattern(regexp = MC.Regex.PHONE_RE, message = MC.Regex.PHONE_RE_MSG)private String phone;@Schema(description = "微信号码")private String wechat;@Schema(description = "电子邮件")@Pattern(regexp = MC.Regex.EMAIL_RE, message = MC.Regex.EMAIL_RE_MSG)private String email;@Schema(description = "员工性别")@Min(value = 0, message = "性别必须为0、1或2")@Max(value = 2, message = "性别必须为0、1或2")private Integer gender;@Schema(description = "员工年龄")@Min(value = 18, message = "年龄不能小于18岁")@Max(value = 60, message = "年龄不能大于60岁")private Integer age;@Schema(description = "所在省份")@Pattern(regexp = MC.Regex.PROVINCE_RE, message = MC.Regex.PROVINCE_RE_MSG)private String province;@Schema(description = "详细住址")@Pattern(regexp = MC.Regex.ADDRESS_RE, message = MC.Regex.ADDRESS_RE_MSG)private String address;@Schema(description = "真实姓名")@Pattern(regexp = MC.Regex.REALNAME_RE, message = MC.Regex.REALNAME_RE_MSG)private String realname;@Schema(description = "身份证号")@Pattern(regexp = MC.Regex.ID_CARD_RE, message = MC.Regex.ID_CARD_RE_MSG)private String idcard;@Schema(description = "部门外键")private Long fkDeptId;@Schema(description = "描述信息")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;@Schema(description = "入职日期")private LocalDateTime hiredate;
}
负责(分页)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Schema(description = "按条件分页搜索员工DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpPageDTO extends PageDTO {@Schema(description = "登录账号")private String username;@Schema(description = "手机号码")private String phone;@Schema(description = "身份证号")private String idcard;@Schema(description = "真实姓名")private String realname;@Schema(description = "部门外键")private Long fkDeptId;
}
负责(全查)业务的实体类:
package com.joezhou.vo; /** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpVO implements Serializable { /** 主键 */ private Long id; /** 真实姓名 */ private String realname;
}
- 开发数据层代码:
package com.joezhou.mapper;/** @author 周航宇 */
@Repository
public interface EmpMapper {@Insert("""insert into ums_emp (username, password, avatar, phone, wechat, email, gender, age, province, address, realname, idcard, info, fk_dept_id, hiredate, version, deleted, created, updated)values (#{username}, #{password}, #{avatar}, #{phone}, #{wechat}, #{email}, #{gender}, #{age}, #{province}, #{address}, #{realname}, #{idcard}, #{info}, #{fkDeptId}, #{hiredate}, #{version}, #{deleted}, #{created}, #{updated})""")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(Emp emp);@Results(id = "empResultMap", value = {@Result(property = "id", column = "id", id = true),@Result(property = "fkDeptId", column = "fk_dept_id"),@Result(property = "dept", column = "fk_dept_id", one = @One(select = "com.joezhou.mapper.DeptMapper.select")),})@Select("""select * from ums_emp twhere t.id = #{param1} and t.deleted = 0""")Emp select(Long id);@ResultMap("empResultMap")@Select("""<script>select * from ums_emp t<where><if test='username != null'> username like concat('%', #{username}, '%') and </if><if test='realname != null'> realname like concat('%', #{realname}, '%') and </if><if test='phone != null'> phone = #{phone} and </if><if test='idcard != null'> idcard = #{idcard} and </if><if test='fkDeptId != null'> fk_dept_id = #{fkDeptId} and </if>t.deleted = 0</where></script>""")List<Emp> list(EmpPageDTO dto);@Update("""<script>update ums_emp<set><if test='username != null'> username = #{username}, </if><if test='password != null'> password = #{password}, </if><if test='avatar != null'> avatar = #{avatar}, </if><if test='phone != null'> phone = #{phone}, </if><if test='wechat != null'> wechat = #{wechat}, </if><if test='email != null'> email = #{email}, </if><if test='gender != null'> gender = #{gender}, </if><if test='age != null'> age = #{age}, </if><if test='province != null'> province = #{province}, </if><if test='address != null'> address = #{address}, </if><if test='realname != null'> realname = #{realname}, </if><if test='idcard != null'> idcard = #{idcard}, </if><if test='fkDeptId != null'> fk_dept_id = #{fkDeptId}, </if><if test='info != null'> info = #{info}, </if><if test='hiredate != null'> hiredate = #{hiredate}, </if><if test='deleted != null'> deleted = #{deleted}, </if><if test='created != null'> created = #{created}, </if><if test='updated != null'> updated = #{updated}, </if>version = version + 1</set>where id = #{id} and deleted = 0 and version = #{version}</script>""")int update(Emp emp);@Update("""update ums_emp set deleted = 1, updated = current_timestampwhere id = #{param1}""")int delete(Long id);@Update("""<script>update ums_emp set deleted = 1, updated = current_timestampwhere id in<foreach collection='list' item='e' open='(' close=')' separator=','>${e}</foreach></script>""")int deleteBatch(List<Long> ids);
}
- 开发业务层代码:
package com.joezhou.service;/** @author 周航宇 */
public interface EmpService {int insert(EmpInsertDTO dto);Emp select(Long id);List<EmpVO> list();PageInfo<Emp> page(EmpPageDTO dto);int update(EmpUpdateDTO dto);int delete(Long id);int deleteBatch(List<Long> ids);
}
package com.joezhou.service.impl;/** @author 周航宇 */
@Service
@CacheConfig(cacheNames = "emp")
public class EmpServiceImpl implements EmpService {@Resourceprivate EmpMapper empMapper;@CacheEvict(allEntries = true)@Overridepublic int insert(EmpInsertDTO dto) {String info = dto.getInfo();String idcard = dto.getIdcard();// 拷贝属性Emp emp = BeanUtil.copyProperties(dto, Emp.class);// 密码加密emp.setPassword(SecureUtil.md5(emp.getPassword()));// 设置默认值emp.setInfo(StrUtil.isBlank(info) ? "暂无描述" : info);emp.setAvatar(MC.Emp.DEFAULT_AVATAR);emp.setGender(IdcardUtil.getGenderByIdCard(idcard));emp.setAge(IdcardUtil.getAgeByIdCard(idcard));emp.setProvince(IdcardUtil.getProvinceByIdCard(idcard));emp.setAddress("暂未添加详细住址");emp.setVersion(0L);emp.setDeleted(0);emp.setCreated(LocalDateTime.now());emp.setUpdated(LocalDateTime.now());// DB添加int result = empMapper.insert(emp);if (result <= 0) {throw new ServerErrorException("DB添加失败");}return result;}@Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")@Overridepublic Emp select(Long id) {Emp result = empMapper.select(id);if (ObjectUtil.isNull(result)) {throw new ServerErrorException("记录不存在");}return result;}@Cacheable(key = "#root.methodName", unless = "#result == null")@Overridepublic List<EmpVO> list() {return empMapper.list(new EmpPageDTO()).stream().map(emp -> BeanUtil.copyProperties(emp, EmpVO.class)).collect(Collectors.toList());}@Cacheable(key = "#root.methodName + ':' + #p0.toString()", condition = "#p0 != null", unless = "#result == null")@Overridepublic PageInfo<Emp> page(EmpPageDTO dto) {PageHelper.startPage(dto.getPageNum(), dto.getPageSize());return new PageInfo<>(empMapper.list(dto));}@CacheEvict(allEntries = true)@Transactional@Retryable(retryFor = VersionException.class)@Overridepublic int update(EmpUpdateDTO dto) {Emp emp = empMapper.select(dto.getId());if (ObjectUtil.isNull(emp)) {throw new ServerErrorException("记录不存在");}BeanUtil.copyProperties(dto, emp);// 设置默认值emp.setUpdated(LocalDateTime.now());// DB修改int result = empMapper.update(emp);if (result <= 0) {throw new VersionException("DB修改失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int delete(Long id) {int result = empMapper.delete(id);if (result <= 0) {throw new ServerErrorException("DB逻辑删除失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int deleteBatch(List<Long> ids) {int result = empMapper.deleteBatch(ids);if (result <= 0) {throw new ServerErrorException("DB逻辑批删失败");}return result;}
}
- 开发控制层代码:
package com.joezhou.controller;/** @author 周航宇 */
@Tag(name = "员工模块")
@RestController
@RequestMapping("/api/v1/emp")
public class EmpController {@Resourceprivate EmpService empService;@Operation(summary = "新增 - 单条新增")@PostMapping("insert")public Result<Integer> insert(@RequestBody @Validated EmpInsertDTO dto) {return new Result<>(empService.insert(dto));}@Operation(summary = "查询 - 单条查询")@GetMapping("select/{id}")public Result<Emp> select(@PathVariable("id") Long id) {return new Result<>(empService.select(id));}@Operation(summary = "查询 - 全部记录")@GetMapping("list")public Result<List<EmpVO>> list() {return new Result<>(empService.list());}@Operation(summary = "查询 - 分页查询")@GetMapping("page")public Result<PageInfo<Emp>> page(@Validated EmpPageDTO dto) {return new Result<>(empService.page(dto));}@Operation(summary = "修改 - 单条修改")@PutMapping("update")public Result<Integer> update(@RequestBody @Validated EmpUpdateDTO dto) {return new Result<>(empService.update(dto));}@Operation(summary = "删除 - 单条删除")@DeleteMapping("delete/{id}")public Result<Integer> delete(@PathVariable("id") Long id) {return new Result<>(empService.delete(id));}@Operation(summary = "删除 - 批量删除")@DeleteMapping("deleteBatch")public Result<Integer> deleteBatch(@RequestParam("ids") List<Long> ids) {return new Result<>(empService.deleteBatch(ids));}
}
2. 下载数据报表
- 开发 DTO 实体类:
package com.joezhou.excel;/** @author 周航宇 */
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmpExcel implements Serializable {@ExcelProperty(value = {"员工数据统计表", "工作信息", "真实姓名"})private String realname;@ExcelProperty(value = {"员工数据统计表", "工作信息", "所在部门"})private String deptName;@ExcelProperty(value = {"员工数据统计表", "工作信息", "入职时间"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime hiredate;@ExcelProperty(value = {"员工数据统计表", "个人信息", "手机号码"})private String phone;@ExcelProperty(value = {"员工数据统计表", "个人信息", "微信号码"})private String wechat;@ExcelProperty(value = {"员工数据统计表", "个人信息", "邮箱地址"})private String email;@ExcelProperty(value = {"员工数据统计表", "个人信息", "用户性别"})private String gender;@ExcelProperty(value = {"员工数据统计表", "个人信息", "用户年龄"})private Integer age;@ExcelProperty(value = {"员工数据统计表", "个人信息", "籍贯省份"})private String province;@ExcelProperty(value = {"员工数据统计表", "个人信息", "现居住地"})private String address;@ExcelProperty(value = {"员工数据统计表", "个人信息", "身份证号"})private String idcard;@ExcelProperty(value = {"员工数据统计表", "个人信息", "描述信息"})private String info;@ExcelProperty(value = {"员工数据统计表", "账号信息", "员工账号"})private String username;@ExcelProperty(value = {"员工数据统计表", "账号信息", "创建时间"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime created;@ExcelProperty(value = {"员工数据统计表", "账号信息", "修改时间"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime updated;
}
- 开发业务层代码:
package com.joezhou.service;/*** 获取员工记录的Excel数据** @return 员工记录的Excel数据列表*/
List<EmpExcel> getExcelData();
package com.joezhou.service.impl;@Override
public List<EmpExcel> getExcelData() {return empMapper.list(new EmpPageDTO()).stream().map(emp -> {EmpExcel empExcel = BeanUtil.copyProperties(emp, EmpExcel.class);if (ObjectUtil.isNotNull(emp.getDept())) {empExcel.setDeptName(emp.getDept().getTitle());}empExcel.setGender(MC.Emp.genderFormat(emp.getGender()));return empExcel;}).collect(Collectors.toList());
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 报表打印")
@SneakyThrows
@GetMapping("/excel")
public void excel(HttpServletResponse resp) {EasyExcelUtil.download(resp, "员工统计表", empService.getExcelData());
}
3. 上传员工头像
- 开发业务层代码:
package com.joezhou.service;/*** 上传员工头像** @param newFile 上传员工头像DTO* @param id 员工主键* @return 文件名*/
String uploadAvatar(MultipartFile newFile, Long id);
package com.joezhou.service.impl;@Transactional(rollbackFor = RuntimeException.class)
@CacheEvict(allEntries = true)
@Override
public String uploadAvatar(MultipartFile newFile, Long id) {// 按主键查询记录Emp emp = empMapper.select(id);if (ObjectUtil.isNull(emp)) {throw new ServerErrorException("记录不存在");}// 备份旧文件String oldFile = emp.getAvatar();// 生成新文件名String newFileName = MinioUtil.randomFilename(newFile);// DB更新文件名emp.setAvatar(newFileName);if (empMapper.update(emp) <= 0) {throw new ServerErrorException("DB更新失败");}try {// MinIO删除旧文件(默认文件不删除)if (!MC.Emp.DEFAULT_AVATAR.equals(oldFile)) {MinioUtil.delete(oldFile, MC.MinIO.AVATAR_DIR, MC.MinIO.BUCKET_NAME);}// MinIO上传新文件MinioUtil.upload(newFile, newFileName, MC.MinIO.AVATAR_DIR, MC.MinIO.BUCKET_NAME);} catch (Exception e) {throw new ServerErrorException("MinIO操作失败:" + e.getMessage());}// 返回新文件名return newFileName;
}
- 开发控制层代码:注意上传文件不是 JSON 参数,而是二进制参数,不能使用 @RequestBody 注解:
package com.joezhou.controller;@Operation(summary = "上传 - 员工头像")
@PostMapping("/uploadAvatar/{id}")
public Result<String> uploadAvatar(@RequestParam("avatarFile") MultipartFile avatarFile,@PathVariable("id") Long id) {return new Result<>(empService.uploadAvatar(avatarFile, id));
}
4. 修改登录密码
- 开发 DTO 实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "修改登录密码DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmpUpdatePasswordDTO implements Serializable {@NotNull(message = "员工主键必须不为空")@Schema(description = "员工主键")private Long id;@NotEmpty(message = "旧密码不能为空")@Schema(description = "旧密码")private String oldPassword;@NotEmpty(message = "新密码不能为空")@Schema(description = "新密码")private String newPassword;
}
- 开发数据层代码:
package com.joezhou.mapper;@Update("""update ums_emp set password = #{param1}, updated = now()where password = #{param2} and id = #{param3} and deleted = 0""")
int updatePassword(String newPassword, String oldPassword, Long id);
- 开发业务层代码:
package com.joezhou.service;/*** 修改登录密码** @param dto 登录密码修改DTO* @return 影响条目数*/
int updatePassword(EmpUpdatePasswordDTO dto);
package com.joezhou.service.impl;@CacheEvict(allEntries = true)
@Override
public int updatePassword(EmpUpdatePasswordDTO dto) {String newPassword = SecureUtil.md5(dto.getNewPassword());String oldPassword = SecureUtil.md5(dto.getOldPassword());Long id = dto.getId();int result = empMapper.updatePassword(newPassword, oldPassword, id);if(result <= 0){throw new ServiceException("修改密码失败");}return result;
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "修改 - 员工密码")
@PutMapping("updatePassword")
public Result<Integer> updatePassword(@RequestBody EmpUpdatePasswordDTO dto) {return new Result<>(empService.updatePassword(dto));
}
5. 账号密码登录
- 开发 DTO 实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "员工登录DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginDTO implements Serializable {@Schema(description = "登录账号")@NotEmpty(message = "登录账号不能为空")@Pattern(regexp = MC.Regex.USERNAME_RE, message = MC.Regex.USERNAME_RE_MSG)private String username;@Schema(description = "登录密码")@NotEmpty(message = "登录密码不能为空")@Pattern(regexp = MC.Regex.PASSWORD_RE, message = MC.Regex.PASSWORD_RE_MSG)private String password;
}
package com.joezhou.vo;/** @author 周航宇 */
@Schema(description = "员工登录VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginVO implements Serializable {/** Token令牌 */private String token;/** 该员工信息 */private Emp emp;
}
- 开发数据层代码:
package com.joezhou.mapper;@ResultMap("empResultMap")
@Select("""select * from ums_emp twhere t.username = #{param1} and t.password = #{param2} and t.deleted = 0""")
Emp selectByAccount(String username, String password);
- 开发业务层代码:
package com.joezhou.service;/*** 按账号密码登录** @param dto 登录DTO* @return 登录VO,包含Token和员工菜单列表*/
LoginVO loginByAccount(LoginDTO dto);
package com.joezhou.service.impl;@Override
public LoginVO loginByAccount(LoginDTO dto) {String username = dto.getUsername();// 密码加密String password = SecureUtil.md5(dto.getPassword());// DB查询Emp emp = empMapper.selectByAccount(username, password);if (ObjectUtil.isNull(emp)) {throw new ServerErrorException("账号或密码有误");}// 生成Token令牌String token = JwtUtil.build(emp.getId(), emp.getRealname(), emp.getAvatar());// 组装VO实体类LoginVO loginVO = new LoginVO();loginVO.setToken(token);loginVO.setEmp(emp);return loginVO;
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "登录 - 账号密码")
@PostMapping("loginByAccount")
public Result<LoginVO> loginByAccount(@RequestBody @Validated LoginDTO dto) {return new Result<>(empService.loginByAccount(dto));
}
- 开发 Token 拦截器:
package com.joezhou.component;/** @author 周航宇 */
@Component
public class TokenInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(@NotNull HttpServletRequest request,@NotNull HttpServletResponse response,@NotNull Object handler) {// 若目标方法不是Controller方法,则直接放行if (!(handler instanceof HandlerMethod)) return true;// 若请求头中不存在Token令牌,则返回失效提示String token = request.getHeader("token");if (StrUtil.isEmpty(token)) {throw new TokenExpiredException("Token令牌不存在");}// 解析Token令牌Map<String, Object> verifyResult = JwtUtil.parse(token);// 若Token令牌即将过期,则返回过期提示以及一个新的Token令牌if ((boolean) verifyResult.get("expiringSoon")) {throw new TokenExpiredSoonException((String) verifyResult.get("newToken"));}return true;}
}
- 配置 Token 拦截器:
package com.joezhou.config;/** @author 周航宇 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Resourceprivate TokenInterceptor tokenInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/api/v1/**").excludePathPatterns("/api/v1/emp/loginByAccount");}
}
E03. 开发角色接口
1. 基础增删改查
- 开发 DTO 实体类:
负责(添加)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "角色添加DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RoleInsertDTO implements Serializable {@Schema(description = "角色名称")@NotEmpty(message = "角色名称不能为空")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "角色描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;
}
负责(修改)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "角色修改DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RoleUpdateDTO implements Serializable {@NotNull(message = "主键不能为空")@Schema(description = "主键")private Long id;@Schema(description = "角色名称")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "角色描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;
}
负责(分页)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Schema(description = "按条件分页搜索角色DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RolePageDTO extends PageDTO {@Schema(description = "角色名称")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;
}
负责(全查)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RoleVO implements Serializable {/** 主键 */private Long id;/** 角色名称 */private String title;
}
- 开发数据层代码:
package com.joezhou.mapper;/** @author 周航宇 */
@Repository
public interface RoleMapper {@Insert("""insert into num_role (title, info, version, deleted, created, updated)values (#{title}, #{info}, #{version}, #{deleted}, #{created}, #{updated})""")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(Role role);@Select("""select * from ums_role twhere t.id = #{param1} and t.deleted = 0""")Role select(Long id);@Select("""<script>select * from ums_role t<where><if test='title != null'> title like concat('%', #{title}, '%') and </if>t.deleted = 0</where></script>""")List<Role> list(RolePageDTO dto);@Update("""<script>update ums_role<set><if test='title != null'> title = #{title}, </if><if test='info != null'> info = #{info}, </if><if test='deleted != null'> deleted = #{deleted}, </if><if test='created != null'> created = #{created}, </if><if test='updated != null'> updated = #{updated}, </if>version = version + 1</set>where id = #{id} and deleted = 0 and version = #{version}</script>""")int update(Role role);@Update("""update ums_role set deleted = 1, updated = current_timestampwhere id = #{param1}""")int delete(Long id);@Update("""<script>update ums_role set deleted = 1, updated = current_timestampwhere id in<foreach collection='list' item='e' open='(' close=')' separator=','>${e}</foreach></script>""")int deleteBatch(List<Long> ids);
}
- 开发业务层代码:
package com.joezhou.service;/** @author 周航宇 */
public interface RoleService {int insert(RoleInsertDTO dto);Role select(Long id);List<RoleVO> list();PageInfo<Role> page(RolePageDTO dto);int update(RoleUpdateDTO dto);int delete(Long id);int deleteBatch(List<Long> ids);
}
package com.joezhou.service.impl;/** @author 周航宇 */
@Service
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements RoleService {@Resourceprivate RoleMapper roleMapper;@CacheEvict(allEntries = true)@Overridepublic int insert(RoleInsertDTO dto) {String info = dto.getInfo();// 拷贝属性Role role = BeanUtil.copyProperties(dto, Role.class);// 设置默认值role.setInfo(StrUtil.isBlank(info) ? "暂无描述" : info);role.setVersion(0L);role.setDeleted(0);role.setCreated(LocalDateTime.now());role.setUpdated(LocalDateTime.now());// DB添加int result = roleMapper.insert(role);if (result <= 0) {throw new ServerErrorException("DB添加失败");}return result;}@Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")@Overridepublic Role select(Long id) {Role result = roleMapper.select(id);if (ObjectUtil.isNull(result)) {throw new ServerErrorException("记录不存在");}return result;}@Cacheable(key = "#root.methodName", unless = "#result == null")@Overridepublic List<RoleVO> list() {return roleMapper.list(new RolePageDTO()).stream().map(role -> BeanUtil.copyProperties(role, RoleVO.class)).collect(Collectors.toList());}@Cacheable(key = "#root.methodName + ':' + #p0.toString()", condition = "#p0 != null", unless = "#result == null")@Overridepublic PageInfo<Role> page(RolePageDTO dto) {PageHelper.startPage(dto.getPageNum(), dto.getPageSize());return new PageInfo<>(roleMapper.list(dto));}@CacheEvict(allEntries = true)@Transactional@Retryable(retryFor = VersionException.class)@Overridepublic int update(RoleUpdateDTO dto) {Role role = roleMapper.select(dto.getId());if (ObjectUtil.isNull(role)) {throw new ServerErrorException("记录不存在");}BeanUtil.copyProperties(dto, role);// 设置默认值role.setUpdated(LocalDateTime.now());// DB修改int result = roleMapper.update(role);if (result <= 0) {throw new VersionException("DB修改失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int delete(Long id) {int result = roleMapper.delete(id);if (result <= 0) {throw new ServerErrorException("DB逻辑删除失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int deleteBatch(List<Long> ids) {int result = roleMapper.deleteBatch(ids);if (result <= 0) {throw new ServerErrorException("DB逻辑批删失败");}return result;}
}
- 开发控制层代码:
package com.joezhou.controller;/** @author 周航宇 */
@Tag(name = "角色模块")
@RestController
@RequestMapping("/api/v1/role")
public class RoleController {@Resourceprivate RoleService roleService;@Operation(summary = "新增 - 单条新增")@PostMapping("insert")public Result<Integer> insert(@RequestBody @Validated RoleInsertDTO dto) {return new Result<>(roleService.insert(dto));}@Operation(summary = "查询 - 单条查询")@GetMapping("select/{id}")public Result<Role> select(@PathVariable("id") Long id) {return new Result<>(roleService.select(id));}@Operation(summary = "查询 - 全部记录")@GetMapping("list")public Result<List<RoleVO>> list() {return new Result<>(roleService.list());}@Operation(summary = "查询 - 分页查询")@GetMapping("page")public Result<PageInfo<Role>> page(@Validated RolePageDTO dto) {return new Result<>(roleService.page(dto));}@Operation(summary = "修改 - 单条修改")@PutMapping("update")public Result<Integer> update(@RequestBody @Validated RoleUpdateDTO dto) {return new Result<>(roleService.update(dto));}@Operation(summary = "删除 - 单条删除")@DeleteMapping("delete/{id}")public Result<Integer> delete(@PathVariable("id") Long id) {return new Result<>(roleService.delete(id));}@Operation(summary = "删除 - 批量删除")@DeleteMapping("deleteBatch")public Result<Integer> deleteBatch(@RequestParam("ids") List<Long> ids) {return new Result<>(roleService.deleteBatch(ids));}
}
2. 下载数据报表
- 开发 DTO 实体类:
package com.joezhou.excel;/** @author 周航宇 */
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleExcel implements Serializable {@ExcelProperty(value = {"角色数据统计表", "角色标题"})private String title;@ExcelProperty(value = {"角色数据统计表", "角色描述"})private String info;@ExcelProperty(value = {"角色数据统计表", "首次创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime created;@ExcelProperty(value = {"角色数据统计表", "最后创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime updated;
}
- 开发业务层代码:
package com.joezhou.service;/*** 导出角色记录的Excel数据** @return 角色记录的Excel数据列表*/
List<RoleExcel> getExcelData();
package com.joezhou.service.impl;@Override
public List<RoleExcel> getExcelData() {return roleMapper.list(new RolePageDTO()).stream().map(role -> BeanUtil.copyProperties(role, RoleExcel.class)).collect(Collectors.toList());
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 报表打印")
@SneakyThrows
@GetMapping("/excel")
public void excel(HttpServletResponse resp) {EasyExcelUtil.download(resp, "角色统计表", roleService.getExcelData());
}
3. 查询员工角色
武技:根据员工的 ID 查询该员工的全部角色列表。
- 开发数据层代码:
package com.joezhou.mapper;@Select("""select * from ums_role t2 where id in (select fk_role_id from ums_emp_role t1where t1.fk_emp_id = #{param1} and t1.deleted = 0) and t2.deleted = 0""")
List<Role> listByEmpId(Long empId);
- 开发业务层代码:
package com.joezhou.service;/*** 根据员工主键查询该员工的全部角色列表** @param empId 员工主键* @return 该员工的全部角色列表*/
List<Role> listByEmpId(Long empId);
package com.joezhou.service.impl;@Cacheable(key = "#root.methodName + ':' + #p0", condition = "#p0 != null",unless = "#result == null")
@Override
public List<Role> listByEmpId(Long empId) {return roleMapper.listByEmpId(empId);
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 员工角色")
@GetMapping("listByEmpId/{empId}")
public Result<List<Role>> listByEmpId(@PathVariable("empId") Long empId) {return new Result<>(roleService.listByEmpId(empId));
}
4. 修改员工角色
- 开发数据方法:
package com.joezhou.mapper;@Update("""update ums_emp_role set deleted = 1, updated = now() where fk_emp_id = #{param1}""")
int deleteEmpRoleByEmpId(Long empId);@Insert("""<script>insert into ums_emp_role (fk_emp_id, fk_role_id, version, deleted, created, updated)values<foreach collection='list' item='e' separator=','>(#{e.fkEmpId}, #{e.fkRoleId}, #{e.version}, #{e.deleted}, #{e.created}, #{e.updated})</foreach></script>""")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertEmpRoleBatch(List<EmpRole> empRoles);
- 开发业务层代码:
package com.joezhou.serivce;/*** 根据员工主键修改该员工的角色列表** @param empId 员工主键* @param roleIds 角色主键列表* @return 影响条目数*/
int updateByEmpId(Long empId, List<Long> roleIds);
package com.joezhou.serivce.impl;@CacheEvict(allEntries = true)
@Transactional
@Retryable(retryFor = VersionException.class)
@Override
public int updateByEmpId(Long empId, List<Long> roleIds) {// 删除该员工的全部中间表记录int deleteResult = roleMapper.deleteEmpRoleByEmpId(empId);// 创建该员工的新角色列表List<EmpRole> empRoles = new ArrayList<>();for (Long roleId : roleIds) {EmpRole empRole = new EmpRole();empRole.setFkRoleId(roleId);empRole.setFkEmpId(empId);empRole.setVersion(0L);empRole.setDeleted(0);empRole.setCreated(LocalDateTime.now());empRole.setUpdated(LocalDateTime.now());empRoles.add(empRole);}// 批量添加该员工的角色记录(中间表记录)int insertResult = roleMapper.insertEmpRoleBatch(empRoles);return deleteResult + insertResult;
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "修改 - 员工角色")
@PutMapping("updateByEmpId")
public Result<Integer> updateByEmpId(@RequestParam("empId") Long empId,@RequestParam("roleIds") List<Long> roleIds) {return new Result<>(roleService.updateByEmpId(empId, roleIds));
}
E04. 开发菜单接口
心法:菜单记录需要关联父菜单记录,所以需要事先对实体类进行改造。
改造如下:
package com.joezhou.entity;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Menu implements Serializable {.../** 每条菜单记录对应 1 条父菜单记录 */private Menu parent;
}
1. 基础增删改查
- 开发 DTO 实体类:
负责(添加)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "菜单添加DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MenuInsertDTO implements Serializable {@Schema(description = "菜单名称")@NotEmpty(message = "菜单名称不能为空")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "菜单地址")@Pattern(regexp = MC.Regex.MENU_URL_RE, message = MC.Regex.MENU_URL_RE_MSG)private String url;@Schema(description = "菜单图标")@Pattern(regexp = MC.Regex.MENU_ICON_RE, message = MC.Regex.MENU_ICON_RE_MSG)private String icon;@Schema(description = "父菜单ID")@Min(value = 0, message = "父菜单ID不能小于0")private Long pid;@Schema(description = "菜单描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;
}
负责(修改)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Schema(description = "菜单修改DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MenuUpdateDTO implements Serializable {@NotNull(message = "主键不能为空")@Schema(description = "主键")private Long id;@Schema(description = "菜单名称")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "菜单地址")@Pattern(regexp = MC.Regex.MENU_URL_RE, message = MC.Regex.MENU_URL_RE_MSG)private String url;@Schema(description = "菜单图标")@Pattern(regexp = MC.Regex.MENU_ICON_RE, message = MC.Regex.MENU_ICON_RE_MSG)private String icon;@Schema(description = "父菜单ID")@Min(value = 0, message = "父菜单ID不能小于0")private Long pid;@Schema(description = "菜单描述")@Pattern(regexp = MC.Regex.INFO_RE, message = MC.Regex.INFO_RE_MSG)private String info;
}
负责(分页)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Schema(description = "按条件分页搜索权限DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MenuPageDTO extends PageDTO {@Schema(description = "菜单名称")@Pattern(regexp = MC.Regex.TITLE_RE, message = MC.Regex.TITLE_RE_MSG)private String title;@Schema(description = "父菜单ID")@Min(value = 0, message = "父菜单ID必须大于0")private Long pid;
}
负责(全查)业务的实体类:
package com.joezhou.dto;/** @author 周航宇 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MenuVO implements Serializable {/** 主键 */private Long id;/** 菜单名称 */private String title;/** 父菜单ID */private Long pid;/** 父菜单名称 */private String parentTitle;
}
- 开发数据层代码:
package com.joezhou.mapper;/** @author 周航宇 */
@Repository
public interface MenuMapper {@Insert("""insert into ums_menu (title, url, icon, pid, info, version, deleted, created, updated)values (#{title}, #{url}, #{icon}, #{pid}, #{info}, #{version}, #{deleted}, #{created}, #{updated})""")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(Menu menu);@Results(id = "menuResultMap", value = {@Result(column = "id", property = "id", id = true),@Result(column = "pid", property = "pid"),@Result(column = "pid", property = "parent", one = @One(select = "com.joezhou.mapper.MenuMapper.select")),})@Select("""select * from ums_menu t where t.id = #{param1} and t.deleted = 0""")Menu select(Long id);@ResultMap("menuResultMap")@Select("""<script>select * from ums_menu t<where><if test='title != null'> title like concat('%', #{title}, '%') and </if><if test='pid != null'> pid = #{pid} and </if>t.deleted = 0</where></script>""")List<Menu> list(MenuPageDTO dto);@Update("""<script>update ums_menu<set><if test='title != null'> title = #{title}, </if><if test='url != null'> url = #{url}, </if><if test='icon != null'> icon = #{icon}, </if><if test='pid != null'> pid = #{pid}, </if><if test='info != null'> info = #{info}, </if><if test='deleted != null'> deleted = #{deleted}, </if><if test='created != null'> created = #{created}, </if><if test='updated != null'> updated = #{updated}, </if>version = version + 1</set>where id = #{id} and deleted = 0 and version = #{version}</script>""")int update(Menu menu);@Update("""update ums_menu set deleted = 1, updated = current_timestamp where id = #{param1}""")int delete(Long id);@Update("""<script>update ums_menu set deleted = 1, updated = current_timestampwhere id in<foreach collection='list' item='e' open='(' close=')' separator=','>${e}</foreach></script>""")int deleteBatch(List<Long> ids);
}
- 开发业务层代码:
package com.joezhou.service;/** @author 周航宇 */
public interface MenuService {int insert(MenuInsertDTO dto);Menu select(Long id);List<MenuVO> list();PageInfo<Menu> page(MenuPageDTO dto);int update(MenuUpdateDTO dto);int delete(Long id);int deleteBatch(List<Long> ids);
}
package com.joezhou.service.impl;/** @author 周航宇 */
@Service
@CacheConfig(cacheNames = "menu")
public class MenuServiceImpl implements MenuService {@Resourceprivate MenuMapper menuMapper;@CacheEvict(allEntries = true)@Overridepublic int insert(MenuInsertDTO dto) {String info = dto.getInfo();// 拷贝属性Menu menu = BeanUtil.copyProperties(dto, Menu.class);// 处理父权限if (ObjectUtil.isNull(menu.getPid()) || menu.getPid() == 0) {menu.setPid(0L);menu.setUrl("/");}// 处理权限路径:必须以 "/" 开头String url = menu.getUrl();if (ObjectUtil.isNotNull(url) && !url.startsWith("/")) {menu.setUrl("/" + url);}// 设置默认值menu.setVersion(0L);menu.setDeleted(0);menu.setInfo(StrUtil.isBlank(info) ? "暂无描述" : info);menu.setCreated(LocalDateTime.now());menu.setUpdated(LocalDateTime.now());// DB添加int result = menuMapper.insert(menu);if (result <= 0) {throw new ServerErrorException("DB添加失败");}return result;}@Cacheable(key = "#p0", condition = "#p0 != null", unless = "#result == null")@Overridepublic Menu select(Long id) {Menu result = menuMapper.select(id);if (ObjectUtil.isNull(result)) {throw new ServerErrorException("记录不存在");}return result;}@Cacheable(key = "#root.methodName", unless = "#result == null")@Overridepublic List<MenuVO> list() {return menuMapper.list(new MenuPageDTO()).stream().map(menu -> {MenuVO menuVO = BeanUtil.copyProperties(menu, MenuVO.class);if (ObjectUtil.isNotNull(menu.getParent())) {menuVO.setParentTitle(menu.getParent().getTitle());} else {menuVO.setParentTitle("无");}return menuVO;}).collect(Collectors.toList());}@Cacheable(key = "#root.methodName + ':' + #p0.toString()", condition = "#p0 != null", unless = "#result == null")@Overridepublic PageInfo<Menu> page(MenuPageDTO dto) {PageHelper.startPage(dto.getPageNum(), dto.getPageSize());return new PageInfo<>(menuMapper.list(dto));}@CacheEvict(allEntries = true)@Transactional@Retryable(retryFor = VersionException.class)@Overridepublic int update(MenuUpdateDTO dto) {Menu menu = menuMapper.select(dto.getId());if (ObjectUtil.isNull(menu)) {throw new ServerErrorException("记录不存在");}BeanUtil.copyProperties(dto, menu);// 设置默认值menu.setUpdated(LocalDateTime.now());// DB修改int result = menuMapper.update(menu);if (result <= 0) {throw new VersionException("DB修改失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int delete(Long id) {int result = menuMapper.delete(id);if (result <= 0) {throw new ServerErrorException("DB逻辑删除失败");}return result;}@CacheEvict(allEntries = true)@Overridepublic int deleteBatch(List<Long> ids) {int result = menuMapper.deleteBatch(ids);if (result <= 0) {throw new ServerErrorException("DB逻辑批删失败");}return result;}
}
- 开发控制层代码:
package com.joezhou.controller;/** @author 周航宇 */
@Tag(name = "菜单模块")
@RestController
@RequestMapping("/api/v1/menu")
public class MenuController {@Resourceprivate MenuService menuService;@Operation(summary = "新增 - 单条新增")@PostMapping("insert")public Result<Integer> insert(@RequestBody @Validated MenuInsertDTO dto) {return new Result<>(menuService.insert(dto));}@Operation(summary = "查询 - 单条查询")@GetMapping("select/{id}")public Result<Menu> select(@PathVariable("id") Long id) {return new Result<>(menuService.select(id));}@Operation(summary = "查询 - 全部记录")@GetMapping("list")public Result<List<MenuVO>> list() {return new Result<>(menuService.list());}@Operation(summary = "查询 - 分页查询")@GetMapping("page")public Result<PageInfo<Menu>> page(@Validated MenuPageDTO dto) {return new Result<>(menuService.page(dto));}@Operation(summary = "修改 - 单条修改")@PutMapping("update")public Result<Integer> update(@RequestBody @Validated MenuUpdateDTO dto) {return new Result<>(menuService.update(dto));}@Operation(summary = "删除 - 单条删除")@DeleteMapping("delete/{id}")public Result<Integer> delete(@PathVariable("id") Long id) {return new Result<>(menuService.delete(id));}@Operation(summary = "删除 - 批量删除")@DeleteMapping("deleteBatch")public Result<Integer> deleteBatch(@RequestParam("ids") List<Long> ids) {return new Result<>(menuService.deleteBatch(ids));}
}
2. 下载数据报表
- 开发 DTO 实体类:
package com.joezhou.excel;/** @author 周航宇 */
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.LEFT, verticalAlignment = VerticalAlignmentEnum.CENTER)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MenuExcel implements Serializable {@ExcelProperty(value = {"菜单数据统计表", "菜单标题"})private String title;@ExcelProperty(value = {"菜单数据统计表", "上级标题"})private String parentTitle;@ExcelProperty(value = {"菜单数据统计表", "菜单图标"})private String icon;@ExcelProperty(value = {"菜单数据统计表", "菜单地址"})private String url;@ExcelProperty(value = {"菜单数据统计表", "菜单描述"})private String info;@ExcelProperty(value = {"菜单数据统计表", "首次创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime created;@ExcelProperty(value = {"菜单数据统计表", "最后创建日期"})@DateTimeFormat("yyyy/MM/dd HH:mm:ss")private LocalDateTime updated;
}
- 开发业务层代码:
package com.joezhou.service;/*** 导出菜单记录的Excel数据** @return 菜单记录的Excel数据列表*/
List<MenuExcel> getExcelData();
package com.joezhou.service.impl;@Override
public List<MenuExcel> getExcelData() {return menuMapper.list(new MenuPageDTO()).stream().map(menu -> {MenuExcel menuExcel = BeanUtil.copyProperties(menu, MenuExcel.class);if (menu.getPid().equals(MC.Menu.ROOT_ID)) {menuExcel.setParentTitle("无");} else {menuExcel.setParentTitle(menu.getParent().getTitle());}return menuExcel;}).collect(Collectors.toList());
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 报表打印")
@SneakyThrows
@GetMapping("/excel")
public void excel(HttpServletResponse resp) {EasyExcelUtil.download(resp, "菜单统计表", menuService.getExcelData());
}
3. 查询角色菜单
武技:根据角色的 ID 查询该角色的全部菜单列表。
- 开发数据层代码:
@Select("""select * from ums_menu t2 where id in (select fk_menu_id from ums_role_menu t1where t1.fk_role_id = #{param1} and t1.deleted = 0) and t2.deleted = 0""")
List<Menu> listByRoleId(Long roleId);
- 开发业务层代码:
package com.joezhou.service;/*** 根据角色主键查询该角色的全部菜单列表** @param roleId 角色主键* @return 该角色的全部菜单列表*/
List<Menu> listByRoleId(Long roleId);
package com.joezhou.service.impl;@Cacheable(key = "#root.methodName + ':' + #p0", condition = "#p0 != null", unless = "#result == null")
@Override
public List<Menu> listByRoleId(Long roleId) {return menuMapper.listByRoleId(roleId);
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 角色菜单")
@GetMapping("listByRoleId/{roleId}")
public Result<List<Menu>> listByRoleId(@PathVariable("roleId") Long roleId) {return new Result<>(menuService.listByRoleId(roleId));
}
4. 修改角色菜单
- 开发数据层代码:
package com.joezhou.mapper;@Update("""update ums_role_menu set deleted = 1, updated = now() where fk_role_id = #{param1}""")
int deleteRoleMenuByRoleId(Long roleId);@Insert("""<script>insert into ums_role_menu (fk_role_id, fk_menu_id, version, deleted, created, updated)values<foreach collection='list' item='e' separator=','>(#{e.fkRoleId}, #{e.fkMenuId}, #{e.version}, #{e.deleted}, #{e.created}, #{e.updated})</foreach></script>""")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertRoleMenuBatch(List<RoleMenu> roleMenus);
- 开发业务层代码:
package com.joezhou.service;/*** 根据角色主键修改该角色的菜单列表** @param roleId 角色主键* @param menuIds 菜单主键列表* @return 影响条目数*/
int updateByRoleId(Long roleId, List<Long> menuIds);
package com.joezhou.service.impl;@CacheEvict(allEntries = true)
@Transactional
@Retryable(retryFor = VersionException.class)
@Override
public int updateByRoleId(Long roleId, List<Long> menuIds) {// 删除该角色的全部中间表记录int deleteResult = menuMapper.deleteRoleMenuByRoleId(roleId);// 创建该角色的新菜单列表List<RoleMenu> roleMenus = new ArrayList<>();for (Long menuId : menuIds) {RoleMenu roleMenu = new RoleMenu();roleMenu.setFkRoleId(roleId);roleMenu.setFkMenuId(menuId);roleMenu.setVersion(0L);roleMenu.setDeleted(0);roleMenu.setCreated(LocalDateTime.now());roleMenu.setUpdated(LocalDateTime.now());roleMenus.add(roleMenu);}// 批量添加该角色的菜单记录(中间表记录)int insertResult = menuMapper.insertRoleMenuBatch(roleMenus);return deleteResult + insertResult;
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "修改 - 角色菜单")
@PutMapping("updateByRoleId")
public Result<Integer> updateByRoleId(@RequestParam("roleId") Long roleId,@RequestParam("menuIds") List<Long> menuIds) {return new Result<>(menuService.updateByRoleId(roleId, menuIds));
}
5. 查询员工菜单
- 开发数据层代码:
package com.joezhou.mapper;@Select("""select * from ums_menu t1 where id in(select distinct t2.fk_menu_id from ums_role_menu t2 where t2.fk_role_id in (select t1.fk_role_id from ums_emp_role t1where t1.fk_emp_id = #{param1} and t1.deleted = 0) and t2.deleted = 0) and t1.deleted = 0""")
List<Menu> listByEmpId(Long empId);
- 开发业务层代码:
package com.joezhou.service;/*** 根据员工主键查询该员工的全部菜单列表** @param empId 员工主键* @return 该员工的全部菜单列表*/
List<Menu> listByEmpId(Long empId);
package com.joezhou.service.impl;@Cacheable(key = "#root.methodName + ':' + #p0", condition = "#p0 != null", unless = "#result == null")
@Override
public List<Menu> listByEmpId(Long empId) {return menuMapper.listByEmpId(empId);
}
- 开发控制层代码:
package com.joezhou.controller;@Operation(summary = "查询 - 员工菜单")
@GetMapping("listByEmpId/{empId}")
public Result<List<Menu>> listByEmpId(@PathVariable("empId") Long empId) {return new Result<>(menuService.listByEmpId(empId));
}
Java道经 - 项目 - MyClub - 后台后端(二)
传送门:JP3-1-MyClub项目简介
传送门:JP3-2-MyClub公共服务
传送门:JP3-3-MyClub后台后端(一)
传送门:JP3-3-MyClub后台后端(二)