StudentServiceImpl.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. package com.qmth.exam.reserve.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  4. import com.baomidou.mybatisplus.core.metadata.IPage;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  7. import com.qmth.boot.core.collection.PageResult;
  8. import com.qmth.boot.core.exception.StatusException;
  9. import com.qmth.boot.core.fss.store.FileStore;
  10. import com.qmth.exam.reserve.bean.Constants;
  11. import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO;
  12. import com.qmth.exam.reserve.bean.login.LoginUser;
  13. import com.qmth.exam.reserve.bean.org.OrgInfo;
  14. import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
  15. import com.qmth.exam.reserve.bean.student.StudentInfo;
  16. import com.qmth.exam.reserve.bean.student.StudentReq;
  17. import com.qmth.exam.reserve.bean.student.StudentVO;
  18. import com.qmth.exam.reserve.bean.student.WechatBindReq;
  19. import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
  20. import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
  21. import com.qmth.exam.reserve.cache.impl.OrgCacheService;
  22. import com.qmth.exam.reserve.config.SysProperty;
  23. import com.qmth.exam.reserve.dao.StudentDao;
  24. import com.qmth.exam.reserve.entity.ApplyTaskEntity;
  25. import com.qmth.exam.reserve.entity.StudentApplyEntity;
  26. import com.qmth.exam.reserve.entity.StudentCourseEntity;
  27. import com.qmth.exam.reserve.entity.StudentEntity;
  28. import com.qmth.exam.reserve.enums.AsyncTaskType;
  29. import com.qmth.exam.reserve.enums.EventType;
  30. import com.qmth.exam.reserve.enums.FileUploadType;
  31. import com.qmth.exam.reserve.enums.Role;
  32. import com.qmth.exam.reserve.service.*;
  33. import com.qmth.exam.reserve.template.execute.StudentPhotoUploadService;
  34. import com.qmth.exam.reserve.util.FileUtil;
  35. import com.qmth.exam.reserve.util.PageUtil;
  36. import org.apache.commons.codec.digest.DigestUtils;
  37. import org.apache.commons.collections4.CollectionUtils;
  38. import org.apache.commons.compress.utils.FileNameUtils;
  39. import org.apache.commons.lang3.StringUtils;
  40. import org.slf4j.Logger;
  41. import org.slf4j.LoggerFactory;
  42. import org.springframework.beans.factory.annotation.Autowired;
  43. import org.springframework.stereotype.Service;
  44. import org.springframework.transaction.annotation.Transactional;
  45. import org.springframework.web.multipart.MultipartFile;
  46. import java.io.File;
  47. import java.io.IOException;
  48. import java.util.Arrays;
  49. import java.util.List;
  50. import java.util.Map;
  51. import java.util.StringJoiner;
  52. import java.util.stream.Collectors;
  53. @Service
  54. public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
  55. private static final Logger log = LoggerFactory.getLogger(StudentServiceImpl.class);
  56. @Autowired
  57. private CategoryCacheService categoryCacheService;
  58. @Autowired
  59. private StudentApplyService studentApplyService;
  60. @Autowired
  61. private OperateLogService operateLogService;
  62. @Autowired
  63. private ApplyTaskService applyTaskService;
  64. @Autowired
  65. private FileStore fileStore;
  66. @Autowired
  67. private AsyncTaskService asyncTaskService;
  68. @Autowired
  69. private StudentPhotoUploadService studentPhotoUploadService;
  70. @Autowired
  71. private ApplyTaskCacheService applyTaskCacheService;
  72. @Autowired
  73. private SysProperty sysProperty;
  74. @Autowired
  75. private StudentCourseService studentCourseService;
  76. @Autowired
  77. private OrgCacheService orgCacheService;
  78. @Override
  79. public StudentEntity findByStudentCode(Long applyTaskId, String studentCode) {
  80. if (StringUtils.isEmpty(studentCode)) {
  81. throw new StatusException("学号不能为空");
  82. }
  83. if (applyTaskId == null) {
  84. throw new StatusException("预约任务ID不能为空");
  85. }
  86. LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
  87. wrapper.eq(StudentEntity::getStudentCode, studentCode);
  88. wrapper.eq(StudentEntity::getApplyTaskId, applyTaskId);
  89. return baseMapper.selectOne(wrapper);
  90. }
  91. @Override
  92. public StudentEntity findByOpenIdAndUid(String openId, String uid) {
  93. if (StringUtils.isEmpty(openId)) {
  94. throw new StatusException("微信OID不能为空");
  95. }
  96. LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
  97. wrapper.eq(StudentEntity::getOpenId, openId);
  98. if (StringUtils.isNotEmpty(uid)) {
  99. wrapper.eq(StudentEntity::getUid, uid);
  100. }
  101. return baseMapper.selectOne(wrapper);
  102. }
  103. @Override
  104. public int findStudentApplyNumberById(Long studentId) {
  105. // 只查询applyNumber等少量字段
  106. LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
  107. wrapper.select(StudentEntity::getApplyNumber);
  108. wrapper.eq(StudentEntity::getId, studentId);
  109. StudentEntity stu = this.getOne(wrapper);
  110. if (stu == null) {
  111. log.warn("学生信息不存在!studentId:{}", studentId);
  112. throw new StatusException("学生信息不存在");
  113. }
  114. return stu.getApplyNumber();
  115. }
  116. @Override
  117. public StudentEntity findLessInfoByStudentId(Long studentId) {
  118. // 只查询id、name等少量字段
  119. LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
  120. wrapper.select(StudentEntity::getId, StudentEntity::getName);
  121. wrapper.eq(StudentEntity::getId, studentId);
  122. return this.getOne(wrapper);
  123. }
  124. @Override
  125. public StudentInfo findInfoByStudentId(Long studentId) {
  126. StudentInfo info = baseMapper.findInfoById(studentId);
  127. if (info == null) {
  128. throw new StatusException("学生信息不存在");
  129. }
  130. return info;
  131. }
  132. @Transactional
  133. @Override
  134. public void bindingWechat(WechatBindReq req) {
  135. StudentEntity stu = baseMapper.selectById(req.getStudentId());
  136. if (stu == null) {
  137. throw new StatusException("学生信息不存在");
  138. }
  139. if (StringUtils.isNotEmpty(stu.getOpenId())) {
  140. throw new StatusException("微信账号已绑定");
  141. }
  142. if (StringUtils.isEmpty(req.getOpenId())) {
  143. throw new StatusException("微信OID不能为空");
  144. }
  145. LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
  146. updateWrapper.set(StudentEntity::getOpenId, req.getOpenId());
  147. updateWrapper.set(StudentEntity::getUid, req.getUid());
  148. updateWrapper.eq(StudentEntity::getId, req.getStudentId());
  149. this.update(updateWrapper);
  150. log.info("[WECHAT_BINDING] studentId:{} openId:{} uid:{}", req.getStudentId(), req.getOpenId(), req.getUid());
  151. }
  152. @Transactional
  153. @Override
  154. public void unbindWechatByStudentId(Long studentId) {
  155. StudentEntity stu = baseMapper.selectById(studentId);
  156. if (stu == null) {
  157. throw new StatusException("学生信息不存在");
  158. }
  159. if (StringUtils.isEmpty(stu.getOpenId())) {
  160. return;
  161. }
  162. LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
  163. updateWrapper.set(StudentEntity::getOpenId, null);
  164. updateWrapper.set(StudentEntity::getUid, null);
  165. updateWrapper.eq(StudentEntity::getId, studentId);
  166. this.update(updateWrapper);
  167. log.info("[WECHAT_UNBIND] studentId:{} openId:{} uid:{}", stu.getId(), stu.getOpenId(), stu.getUid());
  168. }
  169. @Override
  170. public List<StudentEntity> listNoFinishStudent(Long taskId, Boolean cancel) {
  171. return this.baseMapper.listNoFinishStudent(taskId, cancel);
  172. }
  173. @Override
  174. public List<CategoryVO> listNoFinishCategory(Long taskId, Boolean cancel) {
  175. return baseMapper.listNoFinishCategory(taskId, cancel);
  176. }
  177. @Override
  178. public PageResult<StudentVO> pageStudent(StudentReq req) {
  179. if (req.getTaskId() == null) {
  180. OrgInfo org = orgCacheService.currentOrg();
  181. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
  182. if (curApplyTask == null) {
  183. return new PageResult<>();
  184. } else {
  185. req.setTaskId(curApplyTask.getTaskId());
  186. }
  187. }
  188. IPage<StudentVO> iPage = baseMapper.pageStudent(new Page<>(req.getPageNumber(), req.getPageSize()), req);
  189. List<StudentVO> recordList = iPage.getRecords();
  190. for (StudentVO student : recordList) {
  191. //添加图片访问前缀
  192. student.setPhotoPath(sysProperty.getServer() + File.separator + student.getPhotoPath());
  193. }
  194. return PageUtil.of(iPage);
  195. }
  196. @Override
  197. @Transactional
  198. public void delete(Long[] studentCourseIds, LoginUser loginUser) {
  199. // 考生科目ID
  200. for(Long studentCourseId : studentCourseIds) {
  201. StudentCourseEntity studentCourseEntity = studentCourseService.getById(studentCourseId);
  202. if(studentCourseEntity == null) {
  203. throw new StatusException("考生科目不存在");
  204. }
  205. Long studentId = studentCourseEntity.getStudentId();
  206. List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
  207. if (CollectionUtils.isNotEmpty(studentApplyList)) {
  208. log.warn("[考生删除] 删除失败,存在预约信息,student_id:{}", studentId);
  209. throw new StatusException("考生存在预约信息,无法删除");
  210. }
  211. StudentEntity studentEntity = this.getById(studentId);
  212. if (studentEntity == null) {
  213. log.warn("[考生删除] 考生不存在,student_id:{}", studentId);
  214. throw new StatusException("考生不存在,无法删除");
  215. }
  216. //教学点管理员
  217. if (loginUser.getRole().equals(Role.TEACHING)) {
  218. Long studentCategoryId = studentEntity.getCategoryId();
  219. Long loginUserCategoryId = loginUser.getCategoryId();
  220. if (!studentCategoryId.equals(loginUserCategoryId)) {
  221. log.warn("[考生删除] student_id:{}, 所在教学点{},登录用户的教学点:{} ", studentId, studentCategoryId, loginUserCategoryId);
  222. throw new StatusException("不能删除其他教学点的考生科目信息");
  223. }
  224. }
  225. // 删除科目信息
  226. studentCourseService.removeById(studentCourseId);
  227. // 考生是否还有科目,没有科目,删除考生
  228. LambdaQueryWrapper<StudentCourseEntity> wrapper = new LambdaQueryWrapper<>();
  229. wrapper.eq(StudentCourseEntity::getStudentId, studentId);
  230. List<StudentCourseEntity> studentCourseList = studentCourseService.list(wrapper);
  231. if (CollectionUtils.isEmpty(studentCourseList)) {
  232. //删除考生信息
  233. this.removeById(studentId);
  234. } else {
  235. //更新考生的预约次数
  236. this.updateStudentApplyNumber(studentId);
  237. }
  238. // 清空缓存
  239. applyTaskCacheService.clearStudentApplyNumberCache(studentId);
  240. //写入日志
  241. operateLogService.insertOperateLog(loginUser.getId(), EventType.DELETE_STUDENT, String.join("-", studentId.toString(), studentCourseId.toString()));
  242. }
  243. }
  244. private List<StudentApplyEntity> getStudentApplyList(Long studentId) {
  245. LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
  246. wrapper.eq(StudentApplyEntity::getStudentId, studentId);
  247. wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
  248. return studentApplyService.list(wrapper);
  249. }
  250. @Override
  251. public void modifyApplyNumber(Long studentId, Long operateId, Integer applyNumber) {
  252. if (applyNumber == null || applyNumber <= 0) {
  253. throw new StatusException("考生的预约次数必须大于0");
  254. }
  255. List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
  256. if (CollectionUtils.isNotEmpty(studentApplyList) && applyNumber < studentApplyList.size()) {
  257. throw new StatusException("考生已预约了" + studentApplyList.size() + "次,不能小于已经预约的次数");
  258. }
  259. LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
  260. updateWrapper.eq(StudentEntity::getId, studentId);
  261. updateWrapper.set(StudentEntity::getApplyNumber, applyNumber);
  262. this.update(updateWrapper);
  263. //写入日志
  264. operateLogService.insertOperateLog(operateId, EventType.MODIFY_APPLY_NUMBER, studentId + "->" + applyNumber);
  265. }
  266. @Override
  267. public void uploadStudentPhotoAsync(Long operateId, MultipartFile file) throws IOException {
  268. ApplyTaskEntity applyTask = applyTaskService.findApplyTask();
  269. if (applyTask == null) {
  270. throw new StatusException("未开启预约任务,无法导入");
  271. }
  272. File tempFile = FileUtil.getFileTemp(".zip");
  273. if (tempFile != null) {
  274. file.transferTo(tempFile);
  275. }
  276. //写入异步任务
  277. Map<String, Object> taskMap = asyncTaskService.saveAsyncTask(AsyncTaskType.UPLOAD_STUDENT_PHOTO, operateId);
  278. taskMap.computeIfAbsent("taskId", v -> applyTask.getId());
  279. taskMap.computeIfAbsent("operateId", v -> operateId);
  280. try {
  281. studentPhotoUploadService.uploadTask(taskMap, tempFile);
  282. } catch (Exception e) {
  283. throw new StatusException(e.getMessage());
  284. }
  285. }
  286. @Override
  287. public void updateStudentApplyNumber(Long studentId) {
  288. baseMapper.updateStudentApplyNumber(studentId);
  289. }
  290. @Override
  291. public void uploadStudentPhoto(Long operateId, MultipartFile file) {
  292. //文件大小限制
  293. if (file.getSize() > Constants.FILE_SIZE_LIMIT) {
  294. throw new StatusException("上传的考生照片大小不能超过500KB");
  295. }
  296. OrgInfo org = orgCacheService.currentOrg();
  297. CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
  298. if (curApplyTask == null) {
  299. throw new StatusException("未开启预约任务,无法导入");
  300. }
  301. //照片必须以考生的身份证命名
  302. String identityNumber = FileNameUtils.getBaseName(file.getOriginalFilename());
  303. LambdaQueryWrapper<StudentEntity> queryWrapper = new LambdaQueryWrapper<>();
  304. queryWrapper.eq(StudentEntity::getIdentityNumber, identityNumber);
  305. queryWrapper.eq(StudentEntity::getApplyTaskId, curApplyTask.getTaskId());
  306. List<StudentEntity> studentList = this.list(queryWrapper);
  307. if (CollectionUtils.isEmpty(studentList)) {
  308. throw new StatusException("考生照片必须以考生的身份证命名");
  309. }
  310. StringJoiner stringJoiner = FileUtil.getDirName(false);
  311. String path = stringJoiner.toString().replaceAll("\\\\", "/");
  312. try {
  313. fileStore.write(path + file.getOriginalFilename(), file.getInputStream(), DigestUtils.md5Hex(file.getInputStream()));
  314. } catch (Exception e) {
  315. log.error("[上传考生头像],上传失败:{}", e.getMessage());
  316. throw new StatusException("考生头像上传失败,请稍后重试");
  317. }
  318. //更新考生照片路径
  319. List<Long> studentIds = studentList.stream().map(StudentEntity::getId).collect(Collectors.toList());
  320. LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
  321. updateWrapper.set(StudentEntity::getPhotoPath, path + file.getOriginalFilename());
  322. updateWrapper.in(StudentEntity::getId, studentIds);
  323. this.update(null, updateWrapper);
  324. }
  325. }