haogh 8 месяцев назад
Родитель
Сommit
d2151e8332
46 измененных файлов с 1629 добавлено и 52 удалено
  1. 11 0
      pom.xml
  2. 27 0
      src/main/java/com/qmth/exam/reserve/bean/BasicExcelRow.java
  3. 27 0
      src/main/java/com/qmth/exam/reserve/bean/Constants.java
  4. 21 0
      src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskReq.java
  5. 19 0
      src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskTypeVO.java
  6. 41 0
      src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskVO.java
  7. 17 6
      src/main/java/com/qmth/exam/reserve/bean/stdapply/StudentExport.java
  8. 30 0
      src/main/java/com/qmth/exam/reserve/bean/student/StudentReq.java
  9. 32 0
      src/main/java/com/qmth/exam/reserve/bean/student/StudentVO.java
  10. 26 0
      src/main/java/com/qmth/exam/reserve/bean/user/UserReq.java
  11. 32 0
      src/main/java/com/qmth/exam/reserve/bean/user/UserSaveReq.java
  12. 45 0
      src/main/java/com/qmth/exam/reserve/bean/user/UserVO.java
  13. 29 0
      src/main/java/com/qmth/exam/reserve/config/FileStoreConfig.java
  14. 54 0
      src/main/java/com/qmth/exam/reserve/controller/admin/AsyncTaskController.java
  15. 72 0
      src/main/java/com/qmth/exam/reserve/controller/admin/StudentAdminController.java
  16. 8 3
      src/main/java/com/qmth/exam/reserve/controller/admin/StudentApplyController.java
  17. 34 2
      src/main/java/com/qmth/exam/reserve/controller/admin/UserController.java
  18. 16 0
      src/main/java/com/qmth/exam/reserve/dao/AsyncTaskDao.java
  19. 5 0
      src/main/java/com/qmth/exam/reserve/dao/StudentDao.java
  20. 6 0
      src/main/java/com/qmth/exam/reserve/dao/UserDao.java
  21. 61 0
      src/main/java/com/qmth/exam/reserve/entity/AsyncTaskEntity.java
  22. 29 0
      src/main/java/com/qmth/exam/reserve/enums/AsyncTaskResult.java
  23. 31 0
      src/main/java/com/qmth/exam/reserve/enums/AsyncTaskStatus.java
  24. 35 0
      src/main/java/com/qmth/exam/reserve/enums/AsyncTaskType.java
  25. 5 1
      src/main/java/com/qmth/exam/reserve/enums/EventType.java
  26. 22 0
      src/main/java/com/qmth/exam/reserve/enums/FileUploadType.java
  27. 20 0
      src/main/java/com/qmth/exam/reserve/service/AsyncTaskService.java
  28. 3 1
      src/main/java/com/qmth/exam/reserve/service/FileUploadService.java
  29. 2 0
      src/main/java/com/qmth/exam/reserve/service/StudentApplyService.java
  30. 12 0
      src/main/java/com/qmth/exam/reserve/service/StudentService.java
  31. 12 0
      src/main/java/com/qmth/exam/reserve/service/UserService.java
  32. 65 0
      src/main/java/com/qmth/exam/reserve/service/impl/AsyncTaskServiceImpl.java
  33. 16 6
      src/main/java/com/qmth/exam/reserve/service/impl/FileUploadServiceImpl.java
  34. 23 0
      src/main/java/com/qmth/exam/reserve/service/impl/StudentApplyServiceImpl.java
  35. 109 1
      src/main/java/com/qmth/exam/reserve/service/impl/StudentServiceImpl.java
  36. 78 0
      src/main/java/com/qmth/exam/reserve/service/impl/UserServiceImpl.java
  37. 110 0
      src/main/java/com/qmth/exam/reserve/template/execute/StudentApplyNoFinishExportService.java
  38. 151 0
      src/main/java/com/qmth/exam/reserve/template/execute/StudentPhotoUploadService.java
  39. 24 0
      src/main/java/com/qmth/exam/reserve/template/export/AsyncExportTaskTemplate.java
  40. 25 0
      src/main/java/com/qmth/exam/reserve/template/upload/AsyncUploadTaskTemplate.java
  41. 4 4
      src/main/java/com/qmth/exam/reserve/util/DateUtil.java
  42. 61 28
      src/main/java/com/qmth/exam/reserve/util/FileUtil.java
  43. 91 0
      src/main/java/com/qmth/exam/reserve/util/ZipUtil.java
  44. 25 0
      src/main/resources/mapper/AsyncTaskMapper.xml
  45. 31 0
      src/main/resources/mapper/StudentMapper.xml
  46. 32 0
      src/main/resources/mapper/UserMapper.xml

+ 11 - 0
pom.xml

@@ -157,6 +157,17 @@
             <artifactId>kernel</artifactId>
             <version>7.0.1</version>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.3.4</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
 
 
         <dependency>

+ 27 - 0
src/main/java/com/qmth/exam/reserve/bean/BasicExcelRow.java

@@ -0,0 +1,27 @@
+package com.qmth.exam.reserve.bean;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class BasicExcelRow implements IModel {
+
+    private static final long serialVersionUID = -1636637759497290429L;
+
+    @ApiModelProperty(value = "sheet页")
+    @ExcelIgnore
+    public Integer sheet;
+
+    @ApiModelProperty(value = "行号")
+    @ExcelIgnore
+    public Integer row;
+
+    @ApiModelProperty(value = "列名和错误原因")
+    @ExcelIgnore
+    public List<String> errorMessage;
+}

+ 27 - 0
src/main/java/com/qmth/exam/reserve/bean/Constants.java

@@ -19,4 +19,31 @@ public interface Constants {
      */
     String CATEGORY_LEVEL = "CATEGORY_LEVEL";
 
+    /**
+     * 异步任务
+     */
+    String ASYNC_TASK = "ASYNC_TASK";
+
+    /**
+     * 文件存储临时目录
+     */
+    String TMP_DIR = "java.io.tmpdir";
+
+    /**
+     * 临时文件前缀
+     */
+    String TEMP_FILE_SUFFIX = "temp";
+
+    /**
+     * excel后缀
+     */
+    String XLSX_PREFIX = ".xlsx";
+
+     /**
+     * 异步下载提示
+     */
+    String ASYNC_TIPS = "正在执行,请在我的任务中查看执行结果";
+
+
+
 }

+ 21 - 0
src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskReq.java

@@ -0,0 +1,21 @@
+package com.qmth.exam.reserve.bean.asynctask;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.bean.PagerReq;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class AsyncTaskReq extends PagerReq implements IModel {
+
+    private static final long serialVersionUID = -8099726179443930947L;
+
+    @ApiModelProperty(value = "操作人ID")
+    private Long operateId;
+
+    @ApiModelProperty(value = "异步任务执行状态")
+    private AsyncTaskStatus status;
+}

+ 19 - 0
src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskTypeVO.java

@@ -0,0 +1,19 @@
+package com.qmth.exam.reserve.bean.asynctask;
+
+import com.qmth.exam.reserve.bean.IModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class AsyncTaskTypeVO implements IModel {
+
+    private static final long serialVersionUID = 2431555912799474111L;
+
+    @ApiModelProperty("异步任务类型")
+    private String type;
+
+    @ApiModelProperty("异步任务类型名称")
+    private String name;
+}

+ 41 - 0
src/main/java/com/qmth/exam/reserve/bean/asynctask/AsyncTaskVO.java

@@ -0,0 +1,41 @@
+package com.qmth.exam.reserve.bean.asynctask;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.enums.AsyncTaskResult;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class AsyncTaskVO implements IModel {
+
+    private static final long serialVersionUID = -3251374414344127272L;
+
+    @ApiModelProperty(value = "任务类型")
+    private AsyncTaskType type;
+
+    @ApiModelProperty(value = "异步任务执行状态")
+    private AsyncTaskStatus status;
+
+    @ApiModelProperty(value = "异步任务执行结果")
+    private AsyncTaskResult result;
+
+    @ApiModelProperty(value = "执行摘要")
+    private String summary;
+
+    @ApiModelProperty(value = "导出文件下载路径")
+    private String exportFilePath;
+
+    @ApiModelProperty(value = "导入文件下载路径")
+    private String importFilePath;
+
+    @ApiModelProperty(value = "执行开始时间")
+    private Long createTime;
+
+    @ApiModelProperty(value = "执行结束时间")
+    private Long updateTime;
+
+}

+ 17 - 6
src/main/java/com/qmth/exam/reserve/bean/stdapply/StudentExport.java

@@ -1,6 +1,9 @@
 package com.qmth.exam.reserve.bean.stdapply;
 
-import com.qmth.boot.tools.excel.annotation.ExcelColumn;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import com.alibaba.excel.annotation.write.style.HeadFontStyle;
+import com.alibaba.excel.annotation.write.style.HeadStyle;
 import com.qmth.exam.reserve.bean.IModel;
 import lombok.Getter;
 import lombok.Setter;
@@ -12,21 +15,29 @@ import lombok.Setter;
  */
 @Getter
 @Setter
+@ColumnWidth(value = 30)
+@HeadStyle(fillForegroundColor = 11)
+@HeadFontStyle(color = 1)
 public class StudentExport implements IModel {
 
-    @ExcelColumn(name = "学号", index = 0)
+    //    @ExcelColumn(name = "学号", index = 0)
+    @ExcelProperty(value = "学号")
     private String studentCode;
 
-    @ExcelColumn(name = "考生姓名", index = 1)
+    //    @ExcelColumn(name = "考生姓名", index = 1)
+    @ExcelProperty(value = "考生姓名")
     private String studentName;
 
-    @ExcelColumn(name = "证件号码", index = 2)
+    //    @ExcelColumn(name = "证件号码", index = 2)
+    @ExcelProperty(value = "证件号码")
     private String identityNumber;
 
-    @ExcelColumn(name = "所属教学点", index = 3)
+    //    @ExcelColumn(name = "所属教学点", index = 3)
+    @ExcelProperty(value = "所属教学点")
     private String teachingName;
 
-    @ExcelColumn(name = "未预约的场次数", index = 4)
+    //    @ExcelColumn(name = "未预约的场次数", index = 4)
+    @ExcelProperty(value = "未预约的场次数")
     private String remainApplyNumber;
 
 }

+ 30 - 0
src/main/java/com/qmth/exam/reserve/bean/student/StudentReq.java

@@ -0,0 +1,30 @@
+package com.qmth.exam.reserve.bean.student;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.bean.PagerReq;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StudentReq extends PagerReq implements IModel {
+
+    private static final long serialVersionUID = 3027134387973584334L;
+
+    @ApiModelProperty(value = "任务ID")
+    private Long taskId;
+
+    @ApiModelProperty(value = "教学点ID")
+    private Long teachingId;
+
+    @ApiModelProperty(value = "考生姓名")
+    private String name;
+
+    @ApiModelProperty(value = "证件号")
+    private String identityNumber;
+
+    @ApiModelProperty(value = "学号")
+    private String studentCode;
+
+}

+ 32 - 0
src/main/java/com/qmth/exam/reserve/bean/student/StudentVO.java

@@ -0,0 +1,32 @@
+package com.qmth.exam.reserve.bean.student;
+
+import com.qmth.exam.reserve.bean.IModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StudentVO implements IModel {
+
+    private static final long serialVersionUID = -7609631099929677911L;
+
+    @ApiModelProperty(value = "id")
+    private Long id;
+
+    @ApiModelProperty(value = "考生姓名")
+    private String name;
+
+    @ApiModelProperty(value = "证件号")
+    private String identityNumber;
+
+    @ApiModelProperty(value = "学号")
+    private String studentCode;
+
+    @ApiModelProperty(value = "教学点")
+    private String teachingName;
+
+    @ApiModelProperty(value = "可预约次数")
+    private Integer applyNumber;
+
+}

+ 26 - 0
src/main/java/com/qmth/exam/reserve/bean/user/UserReq.java

@@ -0,0 +1,26 @@
+package com.qmth.exam.reserve.bean.user;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.bean.PagerReq;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class UserReq extends PagerReq implements IModel {
+
+    private static final long serialVersionUID = 7223137192010228393L;
+
+    @ApiModelProperty(value = "教学点ID")
+    private Long categoryId;
+
+    @ApiModelProperty(value = "用户名称")
+    private String name;
+
+    @ApiModelProperty(value = "登录名称")
+    private String loginName;
+
+    @ApiModelProperty(value = "启用/禁用")
+    private Boolean enable;
+}

+ 32 - 0
src/main/java/com/qmth/exam/reserve/bean/user/UserSaveReq.java

@@ -0,0 +1,32 @@
+package com.qmth.exam.reserve.bean.user;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.enums.Role;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class UserSaveReq implements IModel {
+
+    private static final long serialVersionUID = 5673587192129146469L;
+
+    @ApiModelProperty("id")
+    private Long id;
+
+    @ApiModelProperty("角色")
+    private Role role;
+
+    @ApiModelProperty("用户姓名")
+    private String name;
+
+    @ApiModelProperty("登录名")
+    private String loginName;
+
+    @ApiModelProperty("手机号码")
+    private String mobile;
+
+    @ApiModelProperty("所属教学点")
+    private Long categoryId;
+}

+ 45 - 0
src/main/java/com/qmth/exam/reserve/bean/user/UserVO.java

@@ -0,0 +1,45 @@
+package com.qmth.exam.reserve.bean.user;
+
+import com.qmth.exam.reserve.bean.IModel;
+import com.qmth.exam.reserve.enums.Role;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class UserVO implements IModel {
+
+    private static final long serialVersionUID = 2021552237474027037L;
+
+    @ApiModelProperty("id")
+    private Long id;
+
+    @ApiModelProperty("启用/禁用")
+    private Boolean enable;
+
+    @ApiModelProperty("角色")
+    private Role role;
+
+    @ApiModelProperty("用户姓名")
+    private String name;
+
+    @ApiModelProperty("登录名")
+    private String loginName;
+
+    @ApiModelProperty("手机号码")
+    private String mobile;
+
+    @ApiModelProperty("所属教学点")
+    private Long categoryId;
+
+    @ApiModelProperty("所属教学点名称")
+    private String categoryName;
+
+    @ApiModelProperty("创建时间")
+    private Long createTime;
+
+    @ApiModelProperty("更新时间")
+    private Long updateTime;
+
+}

+ 29 - 0
src/main/java/com/qmth/exam/reserve/config/FileStoreConfig.java

@@ -0,0 +1,29 @@
+package com.qmth.exam.reserve.config;
+
+import com.qmth.boot.core.fss.config.FileStoreProperty;
+import com.qmth.boot.core.fss.service.FileService;
+import com.qmth.boot.core.fss.service.impl.DefaultFileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@Configuration
+public class FileStoreConfig {
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    @Bean("fileService")
+    public FileService fileService() {
+        FileStoreProperty property = new FileStoreProperty();
+        property.setConfig(sysProperty.getConfig());
+        property.setServer(sysProperty.getServer());
+        Map<String,FileStoreProperty> map = new HashMap<>();
+        map.put("com.qmth.fss", property);
+        return new DefaultFileService(map);
+    }
+}

+ 54 - 0
src/main/java/com/qmth/exam/reserve/controller/admin/AsyncTaskController.java

@@ -0,0 +1,54 @@
+package com.qmth.exam.reserve.controller.admin;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskReq;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskTypeVO;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskVO;
+import com.qmth.exam.reserve.bean.login.LoginUser;
+import com.qmth.exam.reserve.controller.BaseController;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import com.qmth.exam.reserve.service.AsyncTaskService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@Api(tags = "【管理端】异步任务相关接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/async")
+@Aac(strict = false, auth = true)
+public class AsyncTaskController extends BaseController {
+
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @ApiOperation(value = "异步任务业务类型")
+    @PostMapping(value = "/type/list")
+    public List<AsyncTaskTypeVO> list() {
+        AsyncTaskType[] values = AsyncTaskType.values();
+        List<AsyncTaskTypeVO> list = new ArrayList<>(values.length);
+        for (AsyncTaskType value : values) {
+            AsyncTaskTypeVO vo = new AsyncTaskTypeVO();
+            vo.setType(value.name());
+            vo.setName(value.getTitle());
+            list.add(vo);
+        }
+        return list;
+    }
+
+    @ApiOperation(value = "异步任务管理分页")
+    @PostMapping(value = "/page")
+    public PageResult<AsyncTaskVO> page(@RequestBody AsyncTaskReq req) {
+        LoginUser loginUser = this.curLoginUser();
+        req.setOperateId(loginUser.getId());
+        return asyncTaskService.pageAsyncTask(req);
+    }
+
+
+
+}

+ 72 - 0
src/main/java/com/qmth/exam/reserve/controller/admin/StudentAdminController.java

@@ -0,0 +1,72 @@
+package com.qmth.exam.reserve.controller.admin;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.bean.login.LoginUser;
+import com.qmth.exam.reserve.bean.student.StudentReq;
+import com.qmth.exam.reserve.bean.student.StudentVO;
+import com.qmth.exam.reserve.controller.BaseController;
+import com.qmth.exam.reserve.enums.Role;
+import com.qmth.exam.reserve.service.StudentService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@RestController
+@Api(tags = "【管理端】考生相关接口")
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + "/admin/student")
+@Aac(strict = false, auth = true)
+public class StudentAdminController extends BaseController {
+
+    @Autowired
+    private StudentService studentService;
+
+    @ApiOperation(value = "考生管理分页")
+    @PostMapping(value = "/page")
+    public PageResult<StudentVO> page(@RequestBody StudentReq req) {
+        LoginUser loginUser = this.curLoginUser();
+        if(loginUser.getRole().equals(Role.TEACHING)) {
+            req.setTeachingId(loginUser.getCategoryId());
+        }
+        return studentService.pageStudent(req);
+    }
+
+    @ApiOperation(value = "删除考生")
+    @PostMapping(value = "/delete")
+    public void delete(@ApiParam("考生ID") @RequestParam Long id) {
+        LoginUser loginUser = this.curLoginUser();
+        studentService.delete(id, loginUser.getId());
+    }
+
+    @ApiOperation(value = "修改考生可预约次数")
+    @PostMapping(value = "/modify/apply/number")
+    public void modifyApplyNumber(@ApiParam("考生ID") @RequestParam Long id, @ApiParam("可预约次数") @RequestParam Integer applyNumber) {
+        LoginUser loginUser = this.curLoginUser();
+        if (!loginUser.getRole().equals(Role.ADMIN)) {
+            throw new StatusException("没有权限");
+        }
+        studentService.modifyApplyNumber(id, loginUser.getId(), applyNumber);
+    }
+
+    @ApiOperation(value = "上传考生的照片")
+    @PostMapping(value = "upload/student/photo")
+    public String uploadStudentPhoto(MultipartFile file) throws IOException {
+        if (!Objects.requireNonNull(file.getOriginalFilename()).endsWith(".zip")) {
+            throw new StatusException("请上传zip文件");
+        }
+        LoginUser loginUser = this.curLoginUser();
+        studentService.uploadStudentPhoto(loginUser.getId(), file);
+        return Constants.ASYNC_TIPS;
+    }
+
+
+}

+ 8 - 3
src/main/java/com/qmth/exam/reserve/controller/admin/StudentApplyController.java

@@ -7,6 +7,7 @@ import com.qmth.boot.core.collection.PageResult;
 import com.qmth.boot.core.exception.StatusException;
 import com.qmth.boot.tools.excel.ExcelWriter;
 import com.qmth.boot.tools.excel.enums.ExcelType;
+import com.qmth.exam.reserve.bean.Constants;
 import com.qmth.exam.reserve.bean.login.LoginUser;
 import com.qmth.exam.reserve.bean.stdapply.*;
 import com.qmth.exam.reserve.controller.BaseController;
@@ -229,8 +230,8 @@ public class StudentApplyController extends BaseController {
 
     @ApiOperation(value = "导出未预约的考生")
     @GetMapping(value = "/export/std/noApply")
-    public void exportNoApplyStudent(@ApiParam("教学点ID") @RequestParam Long teachingId, HttpServletResponse response) {
-        try {
+    public String exportNoApplyStudent(@ApiParam("教学点ID") @RequestParam Long teachingId, HttpServletResponse response) {
+       /* try {
             String fileName = URLEncoder.encode("未完成预约考生信息表", "UTF-8");
             response.setHeader("Content-Disposition", "inline; filename=" + fileName + ".xlsx");
             response.setContentType("application/vnd.ms-excel");
@@ -241,9 +242,13 @@ public class StudentApplyController extends BaseController {
             }
             writer.writeObjects("未完成预约考生", null, StudentExport.class, studentList.iterator());
             writer.output(response.getOutputStream());
+
         } catch (IOException e) {
             throw new StatusException(e.getMessage());
-        }
+        }*/
+        LoginUser user = curLoginUser();
+        studentApplyService.exportNoApplyStudent(teachingId, user.getId());
+        return Constants.ASYNC_TIPS;
     }
 
 }

+ 34 - 2
src/main/java/com/qmth/exam/reserve/controller/admin/UserController.java

@@ -2,14 +2,20 @@ package com.qmth.exam.reserve.controller.admin;
 
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.exam.reserve.bean.login.LoginUser;
+import com.qmth.exam.reserve.bean.user.UserReq;
+import com.qmth.exam.reserve.bean.user.UserSaveReq;
+import com.qmth.exam.reserve.bean.user.UserVO;
 import com.qmth.exam.reserve.controller.BaseController;
 import com.qmth.exam.reserve.service.UserService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 @RestController
 @Api(tags = "用户相关接口")
@@ -22,4 +28,30 @@ public class UserController extends BaseController {
     @Autowired
     private UserService userService;
 
+    @ApiOperation(value = "用户管理分页")
+    @PostMapping(value = "/page")
+    public PageResult<UserVO> page(@RequestBody UserReq req) {
+        return userService.pageUser(req);
+    }
+
+    @ApiOperation(value = "用户新增/编辑")
+    @PostMapping(value = "/save")
+    public void save(@RequestBody UserSaveReq req) {
+        LoginUser user = curLoginUser();
+        userService.saveUser(user, req);
+    }
+
+
+    @ApiOperation(value = "用户重置密码")
+    @PostMapping(value = "/reset/password")
+    public void resetPassword(@ApiParam("用户ID") @RequestParam Long id) {
+        userService.resetPassword(id);
+    }
+
+    @ApiOperation(value = "用户启用/禁用")
+    @PostMapping(value = "/enable")
+    public void enable(@ApiParam("用户ID") @RequestParam Long id, @ApiParam("启用/禁用") @RequestParam Boolean enable) {
+        userService.enable(id, enable);
+    }
+
 }

+ 16 - 0
src/main/java/com/qmth/exam/reserve/dao/AsyncTaskDao.java

@@ -0,0 +1,16 @@
+package com.qmth.exam.reserve.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskReq;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskVO;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+
+/**
+ * @Description 异步任务
+ */
+public interface AsyncTaskDao extends BaseMapper<AsyncTaskEntity> {
+
+    IPage<AsyncTaskVO> pageAsyncTask(Page<AsyncTaskVO> page, AsyncTaskReq req);
+}

+ 5 - 0
src/main/java/com/qmth/exam/reserve/dao/StudentDao.java

@@ -1,8 +1,12 @@
 package com.qmth.exam.reserve.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
 import com.qmth.exam.reserve.bean.student.StudentInfo;
+import com.qmth.exam.reserve.bean.student.StudentReq;
+import com.qmth.exam.reserve.bean.student.StudentVO;
 import com.qmth.exam.reserve.entity.StudentEntity;
 import org.apache.ibatis.annotations.Param;
 
@@ -16,4 +20,5 @@ public interface StudentDao extends BaseMapper<StudentEntity> {
 
     List<CategoryVO> listNoFinishCategory(@Param("taskId") Long taskId, @Param("cancel") Boolean cancel);
 
+    IPage<StudentVO> pageStudent(Page<StudentVO> page, @Param("req") StudentReq req);
 }

+ 6 - 0
src/main/java/com/qmth/exam/reserve/dao/UserDao.java

@@ -1,8 +1,14 @@
 package com.qmth.exam.reserve.dao;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmth.exam.reserve.bean.user.UserReq;
+import com.qmth.exam.reserve.bean.user.UserVO;
 import com.qmth.exam.reserve.entity.UserEntity;
+import org.apache.ibatis.annotations.Param;
 
 public interface UserDao extends BaseMapper<UserEntity> {
 
+    IPage<UserVO> pageUser(Page<UserVO> page, @Param("req") UserReq req);
 }

+ 61 - 0
src/main/java/com/qmth/exam/reserve/entity/AsyncTaskEntity.java

@@ -0,0 +1,61 @@
+package com.qmth.exam.reserve.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.qmth.exam.reserve.entity.base.BaseEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskResult;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @Description 异步任务实体
+ */
+@Getter
+@Setter
+@TableName("t_async_task")
+public class AsyncTaskEntity extends BaseEntity {
+
+    private static final long serialVersionUID = 3657598700857530277L;
+
+    /**
+     * 任务类型
+     */
+    private AsyncTaskType type;
+
+    /**
+     * 执行状态任务状态 INIT:未开始,RUNNING:进行中,FINISH:已完成
+     */
+    private AsyncTaskStatus status;
+
+    /**
+     * 执行摘要
+     */
+    private String summary;
+
+    /**
+     * 执行结果,SUCCESS:成功,ERROR:失败
+     */
+    private AsyncTaskResult result;
+
+    /**
+     * 导入文件名称
+     */
+    private String importFileName;
+
+    /**
+     * 导入文件路径
+     */
+    private String importFilePath;
+
+    /**
+     * 导出文件路径
+     */
+    private String exportFilePath;
+
+    /**
+     * 操作人ID
+     */
+    private Long operateId;
+
+}

+ 29 - 0
src/main/java/com/qmth/exam/reserve/enums/AsyncTaskResult.java

@@ -0,0 +1,29 @@
+package com.qmth.exam.reserve.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * @Description 异步任务执行结果
+ */
+@Getter
+@AllArgsConstructor
+public enum AsyncTaskResult {
+
+    SUCCESS("成功"),
+
+    ERROR("失败");
+
+    private final String title;
+
+    public static String convertToName(String title) {
+        for (AsyncTaskResult e : AsyncTaskResult.values()) {
+            if (Objects.equals(title, e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 31 - 0
src/main/java/com/qmth/exam/reserve/enums/AsyncTaskStatus.java

@@ -0,0 +1,31 @@
+package com.qmth.exam.reserve.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * @Description 异步任务执行状态
+ */
+@Getter
+@AllArgsConstructor
+public enum AsyncTaskStatus {
+
+    INIT("初始化"),
+
+    RUNNING("进行中"),
+
+    FINISH("已完成");
+
+    private final String title;
+
+    public static String convertToName(String title) {
+        for (AsyncTaskStatus e : AsyncTaskStatus.values()) {
+            if (Objects.equals(title, e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 35 - 0
src/main/java/com/qmth/exam/reserve/enums/AsyncTaskType.java

@@ -0,0 +1,35 @@
+package com.qmth.exam.reserve.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * @Description 异步任务执行业务类型
+ */
+@Getter
+@AllArgsConstructor
+public enum AsyncTaskType {
+
+    STUDENT_IMPORT("考生导入"),
+
+    STUDENT_APPLY_EXPORT("考生预约详情导出"),
+
+    NO_FINISH_APPLY_STUDENT("未完成预约考生导出"),
+
+    UPLOAD_STUDENT_PHOTO("上传考生头像"),
+
+    AUTO_ASSIGN("自动分配");
+
+    private final String title;
+
+    public static String convertToName(String title) {
+        for (AsyncTaskType e : AsyncTaskType.values()) {
+            if (Objects.equals(title, e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 5 - 1
src/main/java/com/qmth/exam/reserve/enums/EventType.java

@@ -15,7 +15,11 @@ public enum EventType {
 
     SAVE_NOTICE("保存编辑考试时段"),
 
-    ENABLE_APPLY_TASK("开启禁用预约任务");
+    ENABLE_APPLY_TASK("开启禁用预约任务"),
+
+    MODIFY_APPLY_NUMBER("修改预约次数"),
+
+    DELETE_STUDENT("删除考生");
 
     private String name;
 

+ 22 - 0
src/main/java/com/qmth/exam/reserve/enums/FileUploadType.java

@@ -0,0 +1,22 @@
+package com.qmth.exam.reserve.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+@Getter
+@AllArgsConstructor
+public enum FileUploadType {
+
+    /**
+     * 系统相关
+     */
+    UPLOAD("upload", "private"),
+    /**
+     * 导入导出
+     */
+    FILE("file", "public");
+
+    private final String title;
+    private final String fssType;
+}

+ 20 - 0
src/main/java/com/qmth/exam/reserve/service/AsyncTaskService.java

@@ -0,0 +1,20 @@
+package com.qmth.exam.reserve.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskReq;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskVO;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+
+import java.util.Map;
+
+/**
+ * @Description 异步任务
+ */
+public interface AsyncTaskService extends IService<AsyncTaskEntity> {
+
+    Map<String,Object> saveAsyncTask(AsyncTaskType type, Long operateId);
+
+    PageResult<AsyncTaskVO> pageAsyncTask(AsyncTaskReq req);
+}

+ 3 - 1
src/main/java/com/qmth/exam/reserve/service/FileUploadService.java

@@ -4,7 +4,9 @@ import java.io.File;
 
 public interface FileUploadService {
 
-    String uploadFile(String dirName, File file);
+    void uploadFile(String dirName, File file);
 
     String uploadByText(String dirName, String text, String fileName);
+
+    File downloadFile(String path, File localFile);
 }

+ 2 - 0
src/main/java/com/qmth/exam/reserve/service/StudentApplyService.java

@@ -34,4 +34,6 @@ public interface StudentApplyService extends IService<StudentApplyEntity> {
     List<StudentApplyExport> listStudentApplyExport(Long teachingId);
 
     List<StudentExport> listStudentNoApply(Long teachingId);
+
+    void exportNoApplyStudent(Long teachingId, Long id);
 }

+ 12 - 0
src/main/java/com/qmth/exam/reserve/service/StudentService.java

@@ -1,11 +1,16 @@
 package com.qmth.exam.reserve.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.boot.core.collection.PageResult;
 import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
 import com.qmth.exam.reserve.bean.student.StudentInfo;
+import com.qmth.exam.reserve.bean.student.StudentReq;
+import com.qmth.exam.reserve.bean.student.StudentVO;
 import com.qmth.exam.reserve.bean.student.WechatBindReq;
 import com.qmth.exam.reserve.entity.StudentEntity;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.util.List;
 
 public interface StudentService extends IService<StudentEntity> {
@@ -28,4 +33,11 @@ public interface StudentService extends IService<StudentEntity> {
 
     List<CategoryVO> listNoFinishCategory(Long taskId, Boolean cancel);
 
+    PageResult<StudentVO> pageStudent(StudentReq req);
+
+    void delete(Long studentId, Long operateId);
+
+    void modifyApplyNumber(Long studentId, Long operateId, Integer applyNumber);
+
+    void uploadStudentPhoto(Long operateId, MultipartFile file) throws IOException;
 }

+ 12 - 0
src/main/java/com/qmth/exam/reserve/service/UserService.java

@@ -1,10 +1,22 @@
 package com.qmth.exam.reserve.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.exam.reserve.bean.login.LoginUser;
+import com.qmth.exam.reserve.bean.user.UserReq;
+import com.qmth.exam.reserve.bean.user.UserSaveReq;
+import com.qmth.exam.reserve.bean.user.UserVO;
 import com.qmth.exam.reserve.entity.UserEntity;
 
 public interface UserService extends IService<UserEntity> {
 
     UserEntity findUserByLoginName(Long orgId, String loginName);
 
+    PageResult<UserVO> pageUser(UserReq req);
+
+    void saveUser(LoginUser user, UserSaveReq req);
+
+    void resetPassword(Long id);
+
+    void enable(Long id, Boolean enable);
 }

+ 65 - 0
src/main/java/com/qmth/exam/reserve/service/impl/AsyncTaskServiceImpl.java

@@ -0,0 +1,65 @@
+package com.qmth.exam.reserve.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskReq;
+import com.qmth.exam.reserve.bean.asynctask.AsyncTaskVO;
+import com.qmth.exam.reserve.config.SysProperty;
+import com.qmth.exam.reserve.dao.AsyncTaskDao;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import com.qmth.exam.reserve.service.AsyncTaskService;
+import com.qmth.exam.reserve.util.PageUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Description 异步任务
+ */
+@Service
+public class AsyncTaskServiceImpl extends ServiceImpl<AsyncTaskDao, AsyncTaskEntity> implements AsyncTaskService {
+
+    @Autowired
+    private SysProperty sysProperty;
+
+    @Override
+    public Map<String, Object> saveAsyncTask(AsyncTaskType type, Long operateId) {
+        Map<String, Object> map = new HashMap<>();
+        AsyncTaskEntity taskEntity = new AsyncTaskEntity();
+        taskEntity.setType(type);
+        taskEntity.setStatus(AsyncTaskStatus.INIT);
+        taskEntity.setOperateId(operateId);
+        this.save(taskEntity);
+        map.computeIfAbsent(Constants.ASYNC_TASK, v -> taskEntity);
+        return map;
+    }
+
+    @Override
+    public PageResult<AsyncTaskVO> pageAsyncTask(AsyncTaskReq req) {
+        IPage<AsyncTaskVO> iPage = baseMapper.pageAsyncTask(new Page<>(req.getPageNumber(), req.getPageSize()), req);
+
+        List<AsyncTaskVO> records = iPage.getRecords();
+        String server = sysProperty.getServer();
+        for (AsyncTaskVO record : records) {
+            if (StringUtils.isNotEmpty(record.getImportFilePath())) {
+                record.setImportFilePath(server + record.getImportFilePath());
+            }
+            if (StringUtils.isNotEmpty(record.getExportFilePath())) {
+                record.setExportFilePath(server + record.getExportFilePath());
+            }
+        }
+
+        return PageUtil.of(iPage);
+    }
+
+
+}

+ 16 - 6
src/main/java/com/qmth/exam/reserve/service/impl/FileUploadServiceImpl.java

@@ -2,9 +2,11 @@ package com.qmth.exam.reserve.service.impl;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.InputStream;
 import java.nio.file.Files;
 import java.time.Duration;
 
+import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,18 +26,14 @@ public class FileUploadServiceImpl implements FileUploadService {
     private SysProperty sysProperty;
 
     @Override
-    public String uploadFile(String dirName, File file) {
+    public void uploadFile(String dirName, File file) {
         try {
             OssStore store = new OssStore(sysProperty.getServer(), sysProperty.getConfig());
-            String filePath = dirName + "/" + file.getName();
-            store.write(filePath, Files.newInputStream(file.toPath()), ByteArray.md5(file).toHexString());
-            String url = store.getPresignedUrl(filePath, Duration.ofMinutes(5));
+            store.write(dirName, Files.newInputStream(file.toPath()), ByteArray.md5(file).toHexString());
             store.close();
-            return url;
         } catch (Exception e) {
             log.warn("文件上传出错", e);
         }
-        return null;
     }
 
     @Override
@@ -54,4 +52,16 @@ public class FileUploadServiceImpl implements FileUploadService {
         return null;
     }
 
+    @Override
+    public File downloadFile(String path, File localFile) {
+        try {
+            OssStore store = new OssStore(sysProperty.getServer(), sysProperty.getConfig());
+            InputStream inputStream = store.read(path);
+            FileUtils.copyInputStreamToFile(inputStream, localFile);
+        } catch (Exception e) {
+            log.warn("下载文件出错", e);
+        }
+        return localFile;
+    }
+
 }

+ 23 - 0
src/main/java/com/qmth/exam/reserve/service/impl/StudentApplyServiceImpl.java

@@ -22,10 +22,12 @@ import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
 import com.qmth.exam.reserve.dao.StudentApplyDao;
 import com.qmth.exam.reserve.entity.*;
 import com.qmth.exam.reserve.entity.base.BaseEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
 import com.qmth.exam.reserve.enums.CategoryLevel;
 import com.qmth.exam.reserve.enums.EventType;
 import com.qmth.exam.reserve.enums.Role;
 import com.qmth.exam.reserve.service.*;
+import com.qmth.exam.reserve.template.execute.StudentApplyNoFinishExportService;
 import com.qmth.exam.reserve.util.DateUtil;
 import com.qmth.exam.reserve.util.JsonHelper;
 import com.qmth.exam.reserve.util.PageUtil;
@@ -90,6 +92,12 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     @Autowired
     private StudentAutoAssignService studentAutoAssignService;
 
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @Autowired
+    private StudentApplyNoFinishExportService studentApplyNoFinishExportService;
+
     @Override
     public PageResult<StudentApplyVO> page(StudentApplyReq req) {
         if (req.getTaskId() == null) {
@@ -1018,6 +1026,21 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         return baseMapper.listStudentNoApply(teachingId, getApplyTask().getId(), categoryEntity.getName());
     }
 
+    @Override
+    public void exportNoApplyStudent(Long teachingId, Long operateId) {
+        //写入异步任务
+        Map<String, Object> taskMap = asyncTaskService.saveAsyncTask(AsyncTaskType.NO_FINISH_APPLY_STUDENT,
+                operateId);
+        taskMap.computeIfAbsent("teachingId", v -> teachingId);
+        taskMap.computeIfAbsent("operateId", v -> operateId);
+        try {
+            studentApplyNoFinishExportService.exportTask(taskMap);
+        } catch (Exception e) {
+            throw new StatusException(e.getMessage());
+        }
+
+    }
+
     private String[] getAvailableArr(List<CategoryVO> examSiteList, TimePeriodEntity timePeriod) {
         String[] availableArr = new String[examSiteList.size() + 1];
         availableArr[0] = DateUtil.getStartAndEndTime(timePeriod.getStartTime(), timePeriod.getEndTime());

+ 109 - 1
src/main/java/com/qmth/exam/reserve/service/impl/StudentServiceImpl.java

@@ -2,23 +2,41 @@ package com.qmth.exam.reserve.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.collection.PageResult;
 import com.qmth.boot.core.exception.StatusException;
+import com.qmth.boot.core.fss.store.FileStore;
 import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
 import com.qmth.exam.reserve.bean.student.StudentInfo;
+import com.qmth.exam.reserve.bean.student.StudentReq;
+import com.qmth.exam.reserve.bean.student.StudentVO;
 import com.qmth.exam.reserve.bean.student.WechatBindReq;
 import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
 import com.qmth.exam.reserve.dao.StudentDao;
+import com.qmth.exam.reserve.entity.ApplyTaskEntity;
+import com.qmth.exam.reserve.entity.StudentApplyEntity;
 import com.qmth.exam.reserve.entity.StudentEntity;
-import com.qmth.exam.reserve.service.StudentService;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import com.qmth.exam.reserve.enums.EventType;
+import com.qmth.exam.reserve.service.*;
+import com.qmth.exam.reserve.template.execute.StudentPhotoUploadService;
+import com.qmth.exam.reserve.util.FileUtil;
+import com.qmth.exam.reserve.util.PageUtil;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 @Service
 public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {
@@ -28,6 +46,24 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
     @Autowired
     private CategoryCacheService categoryCacheService;
 
+    @Autowired
+    private StudentApplyService studentApplyService;
+
+    @Autowired
+    private OperateLogService operateLogService;
+
+    @Autowired
+    private ApplyTaskService applyTaskService;
+
+    @Autowired
+    private FileStore fileStore;
+
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @Autowired
+    private StudentPhotoUploadService studentPhotoUploadService;
+
     @Override
     public StudentEntity findByStudentCode(Long applyTaskId, String studentCode) {
         if (StringUtils.isEmpty(studentCode)) {
@@ -146,4 +182,76 @@ public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> i
         return baseMapper.listNoFinishCategory(taskId, cancel);
     }
 
+    @Override
+    public PageResult<StudentVO> pageStudent(StudentReq req) {
+        if(req.getTaskId() == null) {
+            return new PageResult<>();
+        }
+        IPage<StudentVO> iPage = baseMapper.pageStudent(new Page<>(req.getPageNumber(), req.getPageSize()), req);
+        return PageUtil.of(iPage);
+    }
+
+    @Override
+    public void delete(Long studentId, Long operateId) {
+        List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
+        if (CollectionUtils.isNotEmpty(studentApplyList)) {
+            log.warn("[考生删除] 删除失败,存在预约信息,student_id:{}", studentId);
+            throw new StatusException("考生存在预约信息,无法删除");
+        }
+
+        this.removeById(studentId);
+
+        //写入日志
+        operateLogService.insertOperateLog(operateId, EventType.DELETE_STUDENT, studentId.toString());
+    }
+
+    private List<StudentApplyEntity> getStudentApplyList(Long studentId) {
+        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
+        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        return studentApplyService.list(wrapper);
+    }
+
+    @Override
+    public void modifyApplyNumber(Long studentId, Long operateId, Integer applyNumber) {
+        if (applyNumber == null || applyNumber <= 0) {
+            throw new StatusException("考生的预约次数必须大于0");
+        }
+        List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
+        if (CollectionUtils.isNotEmpty(studentApplyList) && applyNumber < studentApplyList.size()) {
+            throw new StatusException("考生已预约了" + studentApplyList.size() + "次,不能小于已经预约的次数");
+        }
+
+        LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StudentEntity::getId, studentId);
+        updateWrapper.set(StudentEntity::getApplyNumber, applyNumber);
+        this.update(updateWrapper);
+
+        //写入日志
+        operateLogService.insertOperateLog(operateId, EventType.MODIFY_APPLY_NUMBER, studentId + "->" + applyNumber);
+    }
+
+    @Override
+    public void uploadStudentPhoto(Long operateId, MultipartFile file) throws IOException {
+        ApplyTaskEntity applyTask = applyTaskService.findApplyTask();
+        if (applyTask == null) {
+            throw new StatusException("未开启预约任务,无法导入");
+        }
+
+        File tempFile = FileUtil.getFileTemp(".zip");
+        if (tempFile != null) {
+            file.transferTo(tempFile);
+        }
+
+        //写入异步任务
+        Map<String, Object> taskMap = asyncTaskService.saveAsyncTask(AsyncTaskType.UPLOAD_STUDENT_PHOTO, operateId);
+        taskMap.computeIfAbsent("taskId", v -> applyTask.getId());
+        taskMap.computeIfAbsent("operateId", v -> operateId);
+        try {
+            studentPhotoUploadService.uploadTask(taskMap, tempFile);
+        } catch (Exception e) {
+            throw new StatusException(e.getMessage());
+        }
+    }
+
 }

+ 78 - 0
src/main/java/com/qmth/exam/reserve/service/impl/UserServiceImpl.java

@@ -1,12 +1,26 @@
 package com.qmth.exam.reserve.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.collection.PageResult;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.exam.reserve.bean.login.LoginUser;
+import com.qmth.exam.reserve.bean.user.UserReq;
+import com.qmth.exam.reserve.bean.user.UserSaveReq;
+import com.qmth.exam.reserve.bean.user.UserVO;
 import com.qmth.exam.reserve.dao.UserDao;
 import com.qmth.exam.reserve.entity.UserEntity;
+import com.qmth.exam.reserve.enums.Role;
 import com.qmth.exam.reserve.service.UserService;
+import com.qmth.exam.reserve.util.PageUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -25,4 +39,68 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements
         return baseMapper.selectOne(wrapper);
     }
 
+    @Override
+    public PageResult<UserVO> pageUser(UserReq req) {
+        IPage<UserVO> iPage = baseMapper.pageUser(new Page<>(req.getPageNumber(), req.getPageSize()), req);
+        return PageUtil.of(iPage);
+    }
+
+    @Override
+    public void saveUser(LoginUser user, UserSaveReq req) {
+        checkUser(req);
+
+        UserEntity userEntity = new UserEntity();
+        userEntity.setOrgId(user.getOrgId());
+        BeanUtils.copyProperties(req, userEntity);
+
+        if(req.getId() == null) {
+            userEntity.setEnable(Boolean.TRUE);
+            userEntity.setPassword(DigestUtils.sha256Hex("123456").toUpperCase());
+            this.save(userEntity);
+        } else {
+            this.updateById(userEntity);
+        }
+    }
+
+    @Override
+    public void resetPassword(Long id) {
+        UserEntity userEntity = getById(id);
+        if (userEntity == null) {
+            log.error("[重置密码] 找不到用户记录:id:{}", id);
+            throw new StatusException("重置失败");
+        }
+
+        LambdaUpdateWrapper<UserEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(UserEntity::getPassword, DigestUtils.sha256Hex("123456").toUpperCase()).eq(UserEntity::getId, id);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public void enable(Long id, Boolean enable) {
+        UserEntity userEntity = getById(id);
+        if (userEntity == null) {
+            log.error("[启用/禁用] 找不到用户记录:id:{}", id);
+            throw new StatusException("启用/禁用失败");
+        }
+
+        LambdaUpdateWrapper<UserEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(UserEntity::getEnable, enable).eq(UserEntity::getId, id);
+        this.update(updateWrapper);
+    }
+
+    private void checkUser(UserSaveReq req) {
+        if (req.getRole() == null) {
+            throw new StatusException("请选择角色");
+        }
+        if (StringUtils.isBlank(req.getLoginName())) {
+            throw new StatusException("登录账号不能为空");
+        }
+        if (StringUtils.isBlank(req.getName())) {
+            throw new StatusException("用户名称不能为空");
+        }
+        if (req.getRole().equals(Role.TEACHING) && req.getCategoryId() == null) {
+            throw new StatusException("请选择所属教学点");
+        }
+    }
+
 }

+ 110 - 0
src/main/java/com/qmth/exam/reserve/template/execute/StudentApplyNoFinishExportService.java

@@ -0,0 +1,110 @@
+package com.qmth.exam.reserve.template.execute;
+
+import com.alibaba.excel.EasyExcel;
+import com.qmth.boot.tools.uuid.FastUUID;
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.bean.stdapply.StudentExport;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskResult;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.AsyncTaskType;
+import com.qmth.exam.reserve.enums.FileUploadType;
+import com.qmth.exam.reserve.service.AsyncTaskService;
+import com.qmth.exam.reserve.service.CategoryService;
+import com.qmth.exam.reserve.service.FileUploadService;
+import com.qmth.exam.reserve.service.StudentApplyService;
+import com.qmth.exam.reserve.template.export.AsyncExportTaskTemplate;
+import com.qmth.exam.reserve.util.DateUtil;
+import com.qmth.exam.reserve.util.FileUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * @Description 未完成预约考生导出异步执行类
+ */
+@Service
+public class StudentApplyNoFinishExportService extends AsyncExportTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(StudentApplyNoFinishExportService.class);
+
+    public static final String OBJ_TITLE = "未完成预约的考生";
+
+    @Autowired
+    private CategoryService categoryService;
+
+    @Autowired
+    private StudentApplyService studentApplyService;
+
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @Autowired
+    private FileUploadService fileUploadService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void exportTask(Map<String, Object> map) {
+        AsyncTaskEntity task = (AsyncTaskEntity) map.get(Constants.ASYNC_TASK);
+        StringJoiner summary = new StringJoiner("\n").add(
+                MessageFormat.format("{0}{1}{2}", DateFormatUtils.format(new Date(), DateUtil.LongDateString), BEGIN_TITLE, OBJ_TITLE));
+        task.setStatus(AsyncTaskStatus.RUNNING);
+        task.setSummary(summary.toString());
+        asyncTaskService.updateById(task);
+        try {
+            Long teachingId = Long.parseLong(map.get("teachingId").toString());
+            List<StudentExport> studentExportList = studentApplyService.listStudentNoApply(teachingId);
+            String url = createExportFile(studentExportList);
+            summary.add(MessageFormat.format("{0}{1}{2}{3}", DateFormatUtils.format(new Date(), DateUtil.LongDateString), FINISH_TITLE,
+                    !CollectionUtils.isEmpty(studentExportList) ? studentExportList.size() : 0, FINISH_SIZE));
+
+            task.setSummary(summary.toString());
+            task.setExportFilePath(url);
+            task.setImportFileName(AsyncTaskType.NO_FINISH_APPLY_STUDENT.getTitle());
+            task.setResult(AsyncTaskResult.SUCCESS);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            task.setResult(AsyncTaskResult.ERROR);
+            summary.add(MessageFormat.format("{0}{1}{2}{3}", DateFormatUtils.format(new Date(), DateUtil.LongDateString),
+                    EXCEPTION_CREATE_TXT_TITLE, EXCEPTION_DATA, e.getMessage()));
+            task.setSummary(summary.toString());
+        } finally {
+            task.setStatus(AsyncTaskStatus.FINISH);
+            asyncTaskService.updateById(task);
+        }
+
+    }
+
+    private String createExportFile(List<StudentExport> studentExportList) throws IOException {
+        File fileTemp = null;
+        try {
+            fileTemp = FileUtil.getFileTemp(Constants.XLSX_PREFIX);
+            EasyExcel.write(fileTemp, StudentExport.class).sheet("未完成预约考生").doWrite(studentExportList);
+            String fileName = FastUUID.get();
+            StringJoiner stringJoiner = FileUtil.getDirName(FileUploadType.UPLOAD, true).add(fileName).add(Constants.XLSX_PREFIX);
+            String path = stringJoiner.toString().replaceAll("\\\\", "/");
+            fileUploadService.uploadFile(path, fileTemp);
+            return path;
+        } finally {
+            if (fileTemp != null) {
+                boolean deleted = fileTemp.delete();
+                if (!deleted) {
+                    log.warn("[未完成预约考生导出] 临时文件删除失败,tempFileName:{}", fileTemp.getAbsolutePath());
+                }
+            }
+        }
+    }
+
+}

+ 151 - 0
src/main/java/com/qmth/exam/reserve/template/execute/StudentPhotoUploadService.java

@@ -0,0 +1,151 @@
+package com.qmth.exam.reserve.template.execute;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.entity.AsyncTaskEntity;
+import com.qmth.exam.reserve.entity.StudentEntity;
+import com.qmth.exam.reserve.enums.AsyncTaskResult;
+import com.qmth.exam.reserve.enums.AsyncTaskStatus;
+import com.qmth.exam.reserve.enums.FileUploadType;
+import com.qmth.exam.reserve.service.AsyncTaskService;
+import com.qmth.exam.reserve.service.StudentService;
+import com.qmth.exam.reserve.template.upload.AsyncUploadTaskTemplate;
+import com.qmth.exam.reserve.util.DateUtil;
+import com.qmth.exam.reserve.util.FileUtil;
+import com.qmth.exam.reserve.util.ZipUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.*;
+
+@Service
+public class StudentPhotoUploadService extends AsyncUploadTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(StudentPhotoUploadService.class);
+
+    public static final String OBJ_TITLE = "考生照片";
+
+    @Autowired
+    private AsyncTaskService asyncTaskService;
+
+    @Autowired
+    private FileStore fileStore;
+
+    @Autowired
+    private StudentService studentService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void uploadTask(Map<String, Object> map, File file) {
+        AsyncTaskEntity task = (AsyncTaskEntity) map.get(Constants.ASYNC_TASK);
+        StringJoiner summary = new StringJoiner("\n").add(
+                MessageFormat.format("{0}{1}{2}", DateFormatUtils.format(new Date(), DateUtil.LongDateString), BEGIN_TITLE, OBJ_TITLE));
+        task.setStatus(AsyncTaskStatus.RUNNING);
+        task.setSummary(summary.toString());
+        asyncTaskService.updateById(task);
+
+        int num = 0;
+        File dirFile = null;
+        try {
+            dirFile = FileUtil.getDir("studentPhoto" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"));
+            if (dirFile == null) {
+                log.error("[考生头像上传] 临时目录创建失败");
+                return;
+            }
+
+            ZipUtil.unzip(file, dirFile.getAbsolutePath());
+            File[] files = dirFile.listFiles();
+            summary.add(MessageFormat.format("{0}{1}{2}", "共有", files != null ? files.length : 0, FINISH_SIZE));
+
+            List<StudentEntity> studentList;
+            StringJoiner stringJoiner = FileUtil.getDirName(FileUploadType.UPLOAD, true);
+            String path = stringJoiner.toString().replaceAll("\\\\", "/");
+            Long taskId = Long.parseLong(map.get("taskId").toString());
+
+            for (File photo : Objects.requireNonNull(files)) {
+                String identityNumber = FilenameUtils.getBaseName(photo.getName());
+                studentList = listStudentByIdentityNumber(identityNumber, taskId);
+                if (CollectionUtils.isEmpty(studentList)) {
+                    log.warn("[考生头像上传] 无法找到考生,证件号码:{}", identityNumber);
+                    summary.add(MessageFormat.format("{0}{1}", "无法找到考生,证件号码:", identityNumber));
+                    continue;
+                }
+                fileStore.write(path + photo.getName(), Files.newInputStream(photo.toPath()), DigestUtils.md5Hex(Files.newInputStream(photo.toPath())));
+                //更新考生的头像
+                updateStudentPhoto(studentList,  path + photo.getName());
+                num++;
+            }
+
+            task.setResult(AsyncTaskResult.SUCCESS);
+
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            task.setResult(AsyncTaskResult.ERROR);
+            summary.add(MessageFormat.format("{0}{1}{2}{3}", DateFormatUtils.format(new Date(), DateUtil.LongDateString),
+                    EXCEPTION_CREATE_TXT_TITLE, EXCEPTION_DATA, e.getMessage()));
+            task.setSummary(summary.toString());
+        } finally {
+            //删除临时文件
+            deleteTempFile(file, dirFile);
+            summary.add(MessageFormat.format("{0}{1}{2}", FINISH_TITLE, num, FINISH_SIZE));
+            task.setSummary(summary.toString());
+            task.setStatus(AsyncTaskStatus.FINISH);
+            asyncTaskService.updateById(task);
+        }
+
+    }
+
+    private void deleteTempFile(File tempFile, File dirFile) {
+        boolean deleted;
+        if (tempFile != null && tempFile.exists()) {
+            deleted = tempFile.delete();
+            if (!deleted) {
+                log.warn("[考生头像上传] 临时文件删除失败,文件:{}", tempFile.getAbsolutePath());
+            }
+        }
+        if (dirFile != null && dirFile.exists()) {
+            File[] listFiles = dirFile.listFiles();
+            if (listFiles != null) {
+                for (File toDeletePhoto : listFiles) {
+                    deleted = toDeletePhoto.delete();
+                    if (!deleted) {
+                        log.warn("[考生头像上传] 删除文件失败,文件:{}", toDeletePhoto.getAbsolutePath());
+                    }
+                }
+            }
+            deleted = dirFile.delete();
+            if (!deleted) {
+                log.warn("[考生头像上传] 删除目录失败,目录:{}", dirFile.getAbsolutePath());
+            }
+        }
+    }
+
+    private void updateStudentPhoto(List<StudentEntity> studentList, String path) {
+        LambdaUpdateWrapper<StudentEntity> updateWrapper;
+        for (StudentEntity studentEntity : studentList) {
+            updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.set(StudentEntity::getPhotoPath, path);
+            updateWrapper.eq(StudentEntity::getId, studentEntity.getId());
+            studentService.update(null, updateWrapper);
+        }
+    }
+
+    private List<StudentEntity> listStudentByIdentityNumber(String identityNumber, Long taskId) {
+        LambdaQueryWrapper<StudentEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StudentEntity::getIdentityNumber, identityNumber.trim());
+        queryWrapper.eq(StudentEntity::getApplyTaskId, taskId);
+        return studentService.list(queryWrapper);
+    }
+}

+ 24 - 0
src/main/java/com/qmth/exam/reserve/template/export/AsyncExportTaskTemplate.java

@@ -0,0 +1,24 @@
+package com.qmth.exam.reserve.template.export;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+/**
+ * @Description 异步任务导出模板类
+ */
+public abstract class AsyncExportTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(AsyncExportTaskTemplate.class);
+    public static final String BEGIN_TITLE = "->开始准备处理导出的";
+    public static final String FINISH_TITLE = "->数据处理结束,共处理了";
+    public static final String FINISH_SIZE = "条数据。";
+    public static final String EXCEPTION_TITLE = "->数据处理发生异常!";
+    public static final String EXCEPTION_DATA = "错误信息:";
+    public static final String EXCEPTION_CREATE_TXT_TITLE = "->创建导出日志时发生异常!";
+
+    @Async
+    public abstract void exportTask(Map<String, Object> map) throws Exception;
+}

+ 25 - 0
src/main/java/com/qmth/exam/reserve/template/upload/AsyncUploadTaskTemplate.java

@@ -0,0 +1,25 @@
+package com.qmth.exam.reserve.template.upload;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * @Description 异步文件上传模板类
+ */
+public abstract class AsyncUploadTaskTemplate {
+
+    private final static Logger log = LoggerFactory.getLogger(AsyncUploadTaskTemplate.class);
+    public static final String BEGIN_TITLE = "->开始准备处理文件上传的";
+    public static final String FINISH_TITLE = "->上传结束,共上传了";
+    public static final String FINISH_SIZE = "个文件。";
+    public static final String EXCEPTION_TITLE = "->上传文件发生异常!";
+    public static final String EXCEPTION_DATA = "错误信息:";
+    public static final String EXCEPTION_CREATE_TXT_TITLE = "->上传时发生异常!";
+
+    @Async
+    public abstract void uploadTask(Map<String, Object> map, File file) throws Exception;
+}

+ 4 - 4
src/main/java/com/qmth/exam/reserve/util/DateUtil.java

@@ -27,6 +27,7 @@ public class DateUtil {
     public final static String LongDateStringWithoutSplit = "yyyyMMddHHmmss";
     public final static String MillisecondDateString = "yyyyMMddHHmmssSSS";
     public final static String HourMinuteString = "HH:mm";
+    public final static String ShortDateStringWithSplit = "yyyy/MM/dd";
 
     private static Map<String, ThreadLocal<SimpleDateFormat>> SimpleDateForamtThreadLocalMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();
 
@@ -34,14 +35,13 @@ public class DateUtil {
         // 初始化常见的日期类型
         synchronized (DateUtil.class) {
             SimpleDateForamtThreadLocalMap.put(ShortDateString, InitThreadLocal(ShortDateString));
-            SimpleDateForamtThreadLocalMap.put(ShortDateStringWithoutSplit,
-                    InitThreadLocal(ShortDateStringWithoutSplit));
+            SimpleDateForamtThreadLocalMap.put(ShortDateStringWithoutSplit, InitThreadLocal(ShortDateStringWithoutSplit));
             SimpleDateForamtThreadLocalMap.put(LongDateString, InitThreadLocal(LongDateString));
             SimpleDateForamtThreadLocalMap.put(LongDateStringWithoutSplit, InitThreadLocal(LongDateStringWithoutSplit));
             SimpleDateForamtThreadLocalMap.put(MillisecondDateString, InitThreadLocal(MillisecondDateString));
             SimpleDateForamtThreadLocalMap.put(ShortTimeString, InitThreadLocal(ShortTimeString));
-            SimpleDateForamtThreadLocalMap.put(ShortTimeStringWithoutSplit,
-                    InitThreadLocal(ShortTimeStringWithoutSplit));
+            SimpleDateForamtThreadLocalMap.put(ShortTimeStringWithoutSplit, InitThreadLocal(ShortTimeStringWithoutSplit));
+            SimpleDateForamtThreadLocalMap.put(ShortDateStringWithSplit, InitThreadLocal(ShortDateStringWithSplit));
         }
     }
 

+ 61 - 28
src/main/java/com/qmth/exam/reserve/util/FileUtil.java

@@ -1,18 +1,20 @@
 package com.qmth.exam.reserve.util;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
 import com.qmth.boot.core.exception.StatusException;
+import com.qmth.exam.reserve.bean.Constants;
+import com.qmth.exam.reserve.enums.FileUploadType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.StringJoiner;
 
 public class FileUtil {
+    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
+
     public static String readFileContent(InputStream in) {
         StringBuilder content = new StringBuilder();
         InputStreamReader streamReader = null;
@@ -32,19 +34,19 @@ public class FileUtil {
             if (streamReader != null) {
                 try {
                     streamReader.close();
-                } catch (IOException e) {
+                } catch (IOException ignored) {
                 }
             }
             if (bufferedReader != null) {
                 try {
                     bufferedReader.close();
-                } catch (IOException e) {
+                } catch (IOException ignored) {
                 }
             }
             if (in != null) {
                 try {
                     in.close();
-                } catch (IOException e) {
+                } catch (IOException ignored) {
                 }
             }
         }
@@ -53,9 +55,8 @@ public class FileUtil {
     /**
      * 获得文件的byte数组
      * 
-     * @param filePath
-     * @return
-     * @throws IOException
+     * @param filePath 文件路径
+     * @return 获得文件的byte数组
      */
     public static byte[] getBytes(String filePath) throws IOException {
         File file = new File(filePath);
@@ -63,10 +64,9 @@ public class FileUtil {
             throw new FileNotFoundException(filePath);
         }
 
-        ByteArrayOutputStream bos = new ByteArrayOutputStream(((int) file.length()));
-        BufferedInputStream in = null;
-        try {
-            in = new BufferedInputStream(new FileInputStream(file));
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(((int) file.length()))) {
+            BufferedInputStream in = null;
+            in = new BufferedInputStream(Files.newInputStream(file.toPath()));
             int bufSize = 1024;
             byte[] buffer = new byte[bufSize];
             int len = 0;
@@ -74,16 +74,49 @@ public class FileUtil {
                 bos.write(buffer, 0, len);
             }
             return bos.toByteArray();
-        } finally {
-            try {
-                if (in != null) {
-                    in.close();
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
+        }
+    }
+
+    public static File getFileTemp(String suffix) throws IOException {
+        File fileTmpDir = new File(System.getProperty(Constants.TMP_DIR));
+        if (!fileTmpDir.exists()) {
+            boolean flag = fileTmpDir.mkdirs();
+            if (!flag) {
+                log.warn("创建临时目录失败");
+                return null;
             }
-            bos.close();
         }
+        File file = File.createTempFile(Constants.TEMP_FILE_SUFFIX, suffix);
+        log.info("getFileTemp_absolutePath:{}", file.getAbsolutePath());
+        return file;
     }
 
+    public static File getDir(String dirPath) {
+        File fileTmpDir = new File(System.getProperty(Constants.TMP_DIR) + File.separator + dirPath);
+        if (!fileTmpDir.exists()) {
+            boolean flag = fileTmpDir.mkdirs();
+            if (!flag) {
+                log.warn("创建临时目录失败");
+                return null;
+            }
+        }
+        return fileTmpDir;
+    }
+
+    public static StringJoiner getDirName(FileUploadType fileUploadType, boolean fileSeparate) {
+        StringJoiner stringJoiner = new StringJoiner("");
+        stringJoiner.add(MessageFormat.format("{0}{1}{2}", fileUploadType.getTitle(), File.separator,
+                DateUtil.format(new Date(), DateUtil.ShortDateStringWithSplit)));
+        if (fileSeparate) {
+            stringJoiner.add(File.separator);
+        }
+        return stringJoiner;
+    }
+
+    public static void main(String[] args) {
+        StringJoiner dirName = getDirName(FileUploadType.UPLOAD, true);
+        System.out.println(dirName.toString());
+    }
+
+
 }

+ 91 - 0
src/main/java/com/qmth/exam/reserve/util/ZipUtil.java

@@ -0,0 +1,91 @@
+package com.qmth.exam.reserve.util;
+
+import com.qmth.boot.core.exception.StatusException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * zip文件 解压帮助类
+ */
+public class ZipUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(ZipUtil.class);
+
+    public static void unzip(File file, String targetFileDir) {
+        ZipFile zipFile;
+        byte[] buf = new byte[1024 * 1024];
+        int readSize;
+        ZipInputStream zis = null;
+        FileOutputStream fos = null;
+
+        try {
+            zipFile = new ZipFile(file);
+            zipFile.close();
+
+            File newDir = new File(targetFileDir);
+            if (!newDir.exists()) {
+                boolean mkdirFlag = newDir.mkdirs();
+                if (!mkdirFlag) {
+                    log.warn("创建目录失败, targetFileDir:{}", targetFileDir);
+                }
+            }
+
+            zis = new ZipInputStream(Files.newInputStream(file.toPath()), Charset.forName("GBK"));
+            ZipEntry zipEntry = zis.getNextEntry();
+            while (null != zipEntry) {
+                String zipEntryName = zipEntry.getName().replace('\\', '/');
+                if (zipEntry.isDirectory()) {
+                    int indexNumber = zipEntryName.lastIndexOf('/');
+                    File entryDirs = new File(targetFileDir + "/" + zipEntryName.substring(0, indexNumber));
+                    boolean mkdirFlag = entryDirs.mkdirs();
+                    if (!mkdirFlag) {
+                        log.warn("创建目录失败, entryDirs:{}", targetFileDir + "/" + zipEntryName.substring(0, indexNumber));
+                    }
+                } else {
+                    try {
+                        fos = new FileOutputStream(targetFileDir + "/" + zipEntryName);
+                        while ((readSize = zis.read(buf, 0, 1024 * 1024)) != -1) {
+                            fos.write(buf, 0, readSize);
+                        }
+                    } catch (Exception e) {
+                        log.error("解压文件失败:{}", e.getMessage());
+                        throw new StatusException("解压失败");
+                    } finally {
+                        try {
+                            if (null != fos) {
+                                fos.close();
+                            }
+                        } catch (IOException e) {
+                            log.error("解压文件失败:{}", e.getMessage());
+                        }
+                    }
+                }
+                zipEntry = zis.getNextEntry();
+            }
+        } catch (IOException e) {
+            log.error("解压文件失败:{}", e.getMessage());
+            throw new StatusException("解压失败");
+        } finally {
+            try {
+                if (null != zis) {
+                    zis.close();
+                }
+                if (null != fos) {
+                    fos.close();
+                }
+            } catch (IOException e) {
+                log.error("解压文件失败:{}", e.getMessage());
+            }
+        }
+    }
+
+}

+ 25 - 0
src/main/resources/mapper/AsyncTaskMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.exam.reserve.dao.AsyncTaskDao">
+
+    <select id="pageAsyncTask" resultType="com.qmth.exam.reserve.bean.asynctask.AsyncTaskVO">
+        SELECT
+        t.create_time createTime,
+        t.update_time updateTime,
+        t.type,
+        t.STATUS,
+        t.result,
+        t.summary,
+        t.import_file_path importFilePath,
+        t.export_file_path exportFilePath
+        FROM
+        t_async_task t
+        <where>
+            <if test="req.operateId != null">
+                and t.operate_id=#{req.operateId}
+            </if>
+        </where>
+        order by t.update_time desc
+    </select>
+
+</mapper>

+ 31 - 0
src/main/resources/mapper/StudentMapper.xml

@@ -45,4 +45,35 @@
           AND s.apply_number - ifnull( ts.nums, 0 ) > 0
     </select>
 
+    <select id="pageStudent" resultType="com.qmth.exam.reserve.bean.student.StudentVO">
+        SELECT
+        s.id,
+        s.NAME,
+        s.identity_number identityNumber,
+        s.student_code studentCode,
+        c.NAME teachingName,
+        s.apply_number applyNumber
+        FROM
+        t_student s,
+        t_category c
+        WHERE
+        s.category_id = c.id
+        <if test="req.taskId != null">
+            and s.apply_task_id=#{req.taskId}
+        </if>
+        <if test="req.teachingId != null ">
+            and s.category_id=#{req.teachingId}
+        </if>
+        <if test="req.name != null and req.name !=''">
+            and s.name like concat('%', #{req.name}, '%')
+        </if>
+        <if test="req.identityNumber != null and req.identityNumber !=''">
+            and s.identity_number=#{req.identityNumber}
+        </if>
+        <if test="req.studentCode != null and req.studentCode !=''">
+            and s.student_code=#{req.studentCode}
+        </if>
+        order by s.update_time desc
+    </select>
+
 </mapper>

+ 32 - 0
src/main/resources/mapper/UserMapper.xml

@@ -2,4 +2,36 @@
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.qmth.exam.reserve.dao.UserDao">
 
+    <select id="pageUser" resultType="com.qmth.exam.reserve.bean.user.UserVO">
+        SELECT
+        u.id,
+        u.ENABLE,
+        u.role,
+        u.NAME,
+        u.login_name loginName,
+        u.mobile,
+        u.category_id categoryId,
+        u.create_time createTime,
+        u.update_time updateTime,
+        g.NAME categoryName
+        FROM
+        t_user u
+        LEFT JOIN t_category g ON g.id = u.category_id
+        <where>
+            <if test="req.categoryId != null">
+                and u.category_id=#{req.categoryId}
+            </if>
+            <if test="req.name != null and req.name !=''">
+                and u.name like concat('%', #{req.name}, '%')
+            </if>
+            <if test="req.loginName != null and req.loginName !=''">
+                and u.login_name = #{req.loginName}
+            </if>
+            <if test="req.enable != null">
+                and u.enable=#{req.enable}
+            </if>
+        </where>
+        order by u.update_time desc
+    </select>
+
 </mapper>