Java道经 - 项目 - MyClub - 后台后端(二)
传送门:JP3-1-MyClub项目简介
传送门:JP3-2-MyClub公共服务
传送门:JP3-3-MyClub后台后端(一)
传送门:JP3-3-MyClub后台后端(二)
文章目录
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 t
where 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_timestamp
where id = #{param1}
""")
int delete(Long id);
@Update("""
<script>
update ums_dept set deleted = 1, updated = current_timestamp
where 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 {
@Resource
private DeptMapper deptMapper;
@CacheEvict(allEntries = true)
@Override
public 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")
@Override
public 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")
@Override
public 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")
@Override
public 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)
@Override
public 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)
@Override
public int delete(Long id) {
int result = deptMapper.delete(id);
if (result <= 0) {
throw new ServerErrorException("DB逻辑删除失败");
}
return result;
}
@CacheEvict(allEntries = true)
@Override
public 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 {
@Resource
private 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 周航宇 */
@ColumnWidth(20)
@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 t
where 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_timestamp
where id = #{param1}
""")
int delete(Long id);
@Update("""
<script>
update ums_emp set deleted = 1, updated = current_timestamp
where 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 {
@Resource
private EmpMapper empMapper;
@CacheEvict(allEntries = true)
@Override
public 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")
@Override
public 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")
@Override
public 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")
@Override
public 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)
@Override
public 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)
@Override
public int delete(Long id) {
int result = empMapper.delete(id);
if (result <= 0) {
throw new ServerErrorException("DB逻辑删除失败");
}
return result;
}
@CacheEvict(allEntries = true)
@Override
public 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 {
@Resource
private 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 周航宇 */
@ColumnWidth(20)
@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 t
where 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 {
@Override
public 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 {
@Resource
private TokenInterceptor tokenInterceptor;
@Override
public 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 t
where 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_timestamp
where id = #{param1}
""")
int delete(Long id);
@Update("""
<script>
update ums_role set deleted = 1, updated = current_timestamp
where 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 {
@Resource
private RoleMapper roleMapper;
@CacheEvict(allEntries = true)
@Override
public 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")
@Override
public 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")
@Override
public 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")
@Override
public 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)
@Override
public 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)
@Override
public int delete(Long id) {
int result = roleMapper.delete(id);
if (result <= 0) {
throw new ServerErrorException("DB逻辑删除失败");
}
return result;
}
@CacheEvict(allEntries = true)
@Override
public 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 {
@Resource
private 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 t1
where 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_timestamp
where 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 {
@Resource
private MenuMapper menuMapper;
@CacheEvict(allEntries = true)
@Override
public 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")
@Override
public 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")
@Override
public 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")
@Override
public 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)
@Override
public 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)
@Override
public int delete(Long id) {
int result = menuMapper.delete(id);
if (result <= 0) {
throw new ServerErrorException("DB逻辑删除失败");
}
return result;
}
@CacheEvict(allEntries = true)
@Override
public 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 {
@Resource
private 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 t1
where 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 t2.fk_menu_id from ums_role_menu t2 where t2.fk_role_id in (
select t1.fk_role_id from ums_emp_role t1
where 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后台后端(二)