管理系统中经典审核功能实现
前言
先简单交代和阐述一下业务背景和逻辑,该系统是一个综合类的音乐系统,上传音乐时,逻辑和qq音乐一样,前端页面就能体现出大概逻辑,如下图所示:
专辑和歌曲是密不可分的,而且歌曲的封面就是对应专辑的封面,提交审核的时候,也是同理,是一起打包提交的,sql层如下图所示:
设计
由于后台管理系统,qq音乐的后台不予开放,那就自己设计一下,初步设想是这样:
- 有一个整体的 通过/拒绝通过 按钮,然后有一个整体的备注(必填)
- 每个歌曲都可以去听,展示其信息,都有对应的 通过/拒绝通过 按钮,而且必须给注释
- 整体通过与局部拒绝互斥,整体拒绝与局部通过互斥,没有全选功能,必须一个一个选
后端的话,
- 需要建一个专辑对应的log表,来储存一些操作信息,比如管理员的id,操作时间,操作类型等等
- 再建音乐的log表,来储存其备注等等
- 并修改申请表中的状态
- 提供其需要接口
逻辑大致如上。
实现
SQL实现
先建两个log表
由于业务逻辑是一次提交处理,本质上专辑的审核和歌曲的审核是父与子的关系,所以直接绑定一个专辑处理的id即可,音乐信息肯定不能去正式音乐表里拿,要从申请的音乐表里拿(这里面是还未通过审核的)
UI设计
动态实现,非静态
后端接口实现
歌曲栏就参考这个ui,给专辑音乐申请的时候,直接写入数据库即可,接口较为简单,下面为实现代码:
public Result applyMusic(ApplyMusicListDto dto) {
// 向数据库里开始插入信息,先校验参数
boolean isEmpty = BeanUtil.isEmpty(dto);
if (isEmpty) return Result.error("参数不许为空!");
// 有两个表 所以分两个表进行插入
try {
List<ApplyMusicDto> musicList = dto.getMusicList();
ApplyAlbumPo applyAlbumPo = new ApplyAlbumPo();
applyAlbumPo.setAlbumId(dto.getAlbumId());
applyAlbumPo.setMusicCount(musicList.size());
applyAlbumPo.setApplyTime(LocalDateTime.now());
applyAlbumMapper.insert(applyAlbumPo);
applyMapper.insertAlbumMusic(applyAlbumPo.getId(), musicList);
} catch (Exception e) {
return Result.error("一个专辑内的歌曲不能重名");
}
// 因为已经提交 还要删除草稿箱的 数据,其实草稿箱使用redis实现更好 但是无所谓了
draftMapper.delDraftMusicList(dto.getAccount());
return Result.ok();
}
然后就是申请处理部分,分为两种请求get和put,get主要就是获取需要处理的专辑列表,和查询详细专辑信息 这两个接口,put请求也分为两个,处理整张专辑、处理单个音乐,里面的业务要求大概下面几点:
- 处理专辑和处理音乐必须分开,保证处理的具体性,必须每个操作都有批注
- 必须所有音乐处理完后,才能处理整张专辑,而且当有音乐未通过时,整张专辑不能审批通过,只能审批拒绝。
- 由于目前没有超级管理员分配任务的功能,管理员间可能出现处理冲突情况,后端使用update的排他读,当处理时(条件where status = 0)发现处理的个数为0,就是说明已经被别人处理,就会让前端进行同步处理(重新获取数据)。
然后就直接列出代码
分页列表查询
public Result queryAlbumPages(Integer status, Integer styleId,
LocalDateTime startDate, LocalDateTime endDate, int page, int size) {
// 去分页查询一下专辑
LambdaQueryWrapper<ApplyAlbumPo> queryWrapper = Wrappers.lambdaQuery();
queryWrapper
.eq(status != null, ApplyAlbumPo::getStatus, status)
.ge(startDate != null, ApplyAlbumPo::getApplyTime, startDate)
.le(endDate != null, ApplyAlbumPo::getApplyTime, endDate);
queryWrapper.orderByDesc(ApplyAlbumPo::getApplyTime);
// 分页查询
Page<ApplyAlbumPo> resultPage = new Page<>(page, size);
Page<ApplyAlbumPo> poPage = applyAlbumMapper.selectPage(resultPage, queryWrapper);
// Po 转 Vo
List<ApplyAlbumVo> voList = poPage.getRecords().stream()
.map(this::convertPoToVo) // 转换方法
.collect(Collectors.toList());
// 获取专辑id的列表
List<Integer> idList = poPage.getRecords().stream().map(ApplyAlbumPo::getAlbumId)
.collect(Collectors.toList());
// 查询到对应专辑的信息
List<AlbumBo> albumBos = musicClient.pageAlbumByIdList(idList);
for (int i = 0; i < voList.size(); i++) {
BeanUtil.copyProperties(albumBos.get(i), voList.get(i));
}
// 包装返回结果
Page<ApplyAlbumVo> voPage = new Page<>();
voPage.setRecords(voList);
voPage.setCurrent(poPage.getCurrent());
voPage.setSize(poPage.getSize());
voPage.setTotal(poPage.getTotal());
return Result.ok(voPage);
}
逻辑比较简单就是需要一些联表操作,这个接口里遇到了跨模块(跨库) 不能再使用SQL层面的联表了,就直接合并对象进行返回即可。
查询具体专辑信息
public Result getApplyAlbumInfoById(Integer id) {
if (id == null) return Result.error("G了");
ApplyAlbumPo applyAlbumPo = applyAlbumMapper.selectById(id);
// Po 转 Vo
ApplyAlbumVo applyAlbumVo = BeanUtil.copyProperties(applyAlbumPo, ApplyAlbumVo.class);
// 获取专辑id的列表
List<Integer> idList = Collections.singletonList(applyAlbumVo.getAlbumId());
// 查询到对应专辑的信息
List<AlbumBo> albumBos = musicClient.pageAlbumByIdList(idList);
BeanUtil.copyProperties(albumBos.get(0), applyAlbumVo);
LambdaQueryWrapper<ApprovalLogAlbumPo> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(ApprovalLogAlbumPo::getApplyAlbumId, id);
ApprovalLogAlbumPo approvalLogAlbumPo = approvalLogAlbumMapper.selectOne(queryWrapper);
if (approvalLogAlbumPo != null) {
// 如果有处理记录
AdminPo adminPo = adminMapper.selectById(approvalLogAlbumPo.getAdminId());
applyAlbumVo.setApplyDes(approvalLogAlbumPo.getDes());
applyAlbumVo.setAdminEmail(adminPo.getEmail());
}
List<ApplyMusicPo> applyMusicList = applyMusicMapper.selectApplyMusicVoList(id);
for (ApplyMusicPo item : applyMusicList) {
if (item.getDuration() != null) {
int temp = Integer.parseInt(item.getDuration());
int minutes = temp / 60;
int seconds = temp % 60;
// 使用String.format来格式化分钟和秒数,确保它们都是两位数
item.setDuration(String.format("%02d:%02d", minutes, seconds));
}
}
applyAlbumVo.setMusicList(applyMusicList);
return Result.ok(applyAlbumVo);
}
这个也比较简单,和上个查询接口基本同理,就是联的表更多了一点,以及由于feign的查询接口时list类型,用一个对象包装了一下,没有其他的难点了。
音乐审批接口
@Override
@Transactional
public Result approvalMusicApply(ApprovalMusicApplyDto dto) {
if (BeanUtil.isEmpty(dto)) return Result.error("参数不能为空");
String des = dto.getDes();
Integer toStatus = dto.getToStatus();
// 申请id
Integer applyId = dto.getId();
if (StringUtils.isBlank(des)) {
return Result.error("批注不能为空");
}
Integer adminId = Integer.valueOf(String.valueOf(session.getAttribute("id")));
// 前面和处理音乐人审批逻辑大致相同,只是根据通过和拒绝不同,对专辑状态也进行操作,通过不操作,但拒绝的话,转移也要改成拒绝状态
// rr级别 去用update操作一下,where是排他锁,可以查到最新数据
LambdaUpdateWrapper<ApplyMusicPo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(ApplyMusicPo::getStatus, toStatus)
.eq(ApplyMusicPo::getId, applyId)
.eq(ApplyMusicPo::getStatus, 0);
int updateCount = applyMusicMapper.update(updateWrapper);
if (updateCount == 0) {
// 说明一定被人改变了,而且是已经提交了的,因为特别这需要考虑专辑的状态和音乐的状态,所以这里就不过度增加代码复杂度了
// 直接前端刷新一下获取最新数据即可
return Result.error("无法执行该操作,已经被其他人处理");
}
// 在音乐处理日志表插入一条信息
ApprovalLogMusicPo approvalLogMusicPo = new ApprovalLogMusicPo();
approvalLogMusicPo.setApplyMusicId(applyId);
approvalLogMusicPo.setApprovalType(toStatus.byteValue());
approvalLogMusicPo.setApprovalTime(LocalDateTime.now());
approvalLogMusicPo.setDes(des);
approvalLogMusicPo.setAdminId(adminId);
approvalLogMusicMapper.insert(approvalLogMusicPo);
return Result.ok();
}
就简单分为四段,校验参数阶段,执行业务兼判断是否有权限阶段(合在了一起),插入日志阶段
专辑审批接口
@Override
@Transactional
public Result approvalAlbumApply(ApprovalAlbumApplyDto dto) {
if (BeanUtil.isEmpty(dto)) {
return Result.error("参数不能为空");
}
String des = dto.getDes();
Integer applyId = dto.getId();
Integer toStatus = dto.getToStatus();
if (StringUtils.isBlank(des)) {
return Result.error("批注不能为空");
}
Integer adminId = Integer.valueOf(String.valueOf(session.getAttribute("id")));
// 要求过滤一下,到底能不能进行该通过或拒绝操作
LambdaQueryWrapper<ApplyMusicPo> queryWrapper1 = Wrappers.lambdaQuery();
queryWrapper1.eq(ApplyMusicPo::getApplyAlbumId, applyId)
.eq(ApplyMusicPo::getStatus, 0);
// 看未处理的有多少
Long unDoCount = applyMusicMapper.selectCount(queryWrapper1);
if (unDoCount != 0) {
// 如果有未处理的话
return Result.error("必须审批完全部音乐,才可以审批整张专辑");
}
// 如果想要通过一张专辑,它的所有音乐必须全部通过
if (toStatus == 1) {
LambdaQueryWrapper<ApplyMusicPo> queryWrapper2 = Wrappers.lambdaQuery();
queryWrapper2.eq(ApplyMusicPo::getApplyAlbumId, applyId)
.eq(ApplyMusicPo::getStatus, 2);
Long doRejectCount = applyMusicMapper.selectCount(queryWrapper1);
if (doRejectCount > 0) {
// 有音乐是拒绝状态
return Result.error("该专辑无法通过,因为有音乐未通过,请重新审批并给上整张专辑的批注");
}
}
// 筛完 然后就可以进行业务操作了
// rr级别 去用update操作一下,where是排他锁,可以查到最新数据
LambdaUpdateWrapper<ApplyAlbumPo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(ApplyAlbumPo::getStatus, toStatus)
.eq(ApplyAlbumPo::getId, applyId)
.eq(ApplyAlbumPo::getStatus, 0);
int updateCount = applyAlbumMapper.update(updateWrapper);
if (updateCount == 0) {
// 说明一定被人改变了,而且是已经提交了的,因为特别这需要考虑专辑的状态和音乐的状态,所以这里就不过度增加代码复杂度了
// 直接前端刷新一下获取最新数据即可
return Result.error("无法执行该操作,帖子已经被其他人处理");
}
// 在音乐处理日志表插入一条信息
ApprovalLogAlbumPo approvalLogAlbumPo = new ApprovalLogAlbumPo();
approvalLogAlbumPo.setApplyAlbumId(applyId);
approvalLogAlbumPo.setApprovalType(toStatus.byteValue());
approvalLogAlbumPo.setApprovalTime(LocalDateTime.now());
approvalLogAlbumPo.setDes(des);
approvalLogAlbumPo.setAdminId(adminId);
approvalLogAlbumMapper.insert(approvalLogAlbumPo);
return Result.ok();
}
这个比上一条多一点逻辑,但也是四步走,校验参数阶段,校验权限、执行业务阶段(由于需要根据update结果判断权限,所以这两个阶段还是比较耦合),插入日志阶段