caozixuan 1 жил өмнө
parent
commit
f392820e41
21 өөрчлөгдсөн 1094 нэмэгдсэн , 117 устгасан
  1. 1 0
      install/mysql/init/init.sql
  2. 3 0
      install/mysql/upgrade/1.1.0.sql
  3. 8 3
      pom.xml
  4. 19 0
      src/main/java/cn/com/qmth/print/manage/controller/ExamController.java
  5. 19 13
      src/main/java/cn/com/qmth/print/manage/controller/ExamStudentController.java
  6. 3 1
      src/main/java/cn/com/qmth/print/manage/controller/SysController.java
  7. 9 2
      src/main/java/cn/com/qmth/print/manage/controller/UserController.java
  8. 11 1
      src/main/java/cn/com/qmth/print/manage/dao/ExamStudentDao.java
  9. 113 68
      src/main/java/cn/com/qmth/print/manage/dto/ExamStudentExportDTO.java
  10. 10 0
      src/main/java/cn/com/qmth/print/manage/entity/ExamStudentEntity.java
  11. 11 0
      src/main/java/cn/com/qmth/print/manage/service/ExamStudentService.java
  12. 2 1
      src/main/java/cn/com/qmth/print/manage/service/UserService.java
  13. 13 1
      src/main/java/cn/com/qmth/print/manage/service/impl/ExamStudentServiceImpl.java
  14. 21 13
      src/main/java/cn/com/qmth/print/manage/service/impl/UserServiceImpl.java
  15. 5 5
      src/main/java/cn/com/qmth/print/manage/service/query/UserQuery.java
  16. 59 0
      src/main/java/cn/com/qmth/print/manage/utils/excel/ExcelField.java
  17. 10 9
      src/main/java/cn/com/qmth/print/manage/utils/excel/ExcelReader.java
  18. 456 0
      src/main/java/cn/com/qmth/print/manage/utils/excel/ExportExcel.java
  19. 296 0
      src/main/java/cn/com/qmth/print/manage/utils/excel/Reflections.java
  20. BIN
      src/main/resources/importtemplates/examImportTemplate.xlsx
  21. 25 0
      src/main/resources/mapper/ExamStudentDao.xml

+ 1 - 0
install/mysql/init/init.sql

@@ -88,6 +88,7 @@ CREATE TABLE `pm_exam_student`
     `sort_no`      bigint(20)                    NOT NULL,
     `name`         varchar(255) COLLATE utf8_bin DEFAULT NULL,
     `course_code`  varchar(255) COLLATE utf8_bin NOT NULL,
+    `course_name` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '课程名称',
     `student_code` varchar(255) COLLATE utf8_bin DEFAULT NULL,
     `exam_number`  varchar(255) COLLATE utf8_bin NOT NULL,
     `exam_site`    varchar(255) COLLATE utf8_bin DEFAULT NULL,

+ 3 - 0
install/mysql/upgrade/1.1.0.sql

@@ -3,3 +3,6 @@ ALTER TABLE pm_user
 
 ALTER TABLE pm_exam_student
     ADD COLUMN exam_unit VARCHAR(255) NULL COMMENT '服务单元' AFTER exam_room;
+
+ALTER TABLE pm_exam_student
+    ADD COLUMN course_name VARCHAR(255) NOT NULL COMMENT '课程名称' AFTER course_code;

+ 8 - 3
pom.xml

@@ -116,17 +116,17 @@
 		<dependency>
 			<groupId>org.apache.poi</groupId>
 			<artifactId>poi</artifactId>
-			<version>3.17</version>
+			<version>3.9</version>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.poi</groupId>
 			<artifactId>poi-ooxml</artifactId>
-			<version>3.17</version>
+			<version>3.9</version>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.poi</groupId>
 			<artifactId>poi-ooxml-schemas</artifactId>
-			<version>3.17</version>
+			<version>3.9</version>
 		</dependency>
 		<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-core -->
 		<dependency>
@@ -141,6 +141,11 @@
 			<artifactId>zip4j</artifactId>
 			<version>1.3.2</version>
 		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>1.2.62</version>
+		</dependency>
 		<dependency>
 			<groupId>org.quartz-scheduler</groupId>
 			<artifactId>quartz</artifactId>

+ 19 - 0
src/main/java/cn/com/qmth/print/manage/controller/ExamController.java

@@ -4,13 +4,18 @@ import cn.com.qmth.print.manage.entity.ExamEntity;
 import cn.com.qmth.print.manage.service.ExamService;
 import cn.com.qmth.print.manage.service.PmSession;
 import cn.com.qmth.print.manage.service.query.ExamQuery;
+import cn.com.qmth.print.manage.utils.PathUtil;
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
 import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.boot.core.exception.StatusException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.File;
+import java.util.Objects;
+
 /**
  * @Date: 2021/11/16.
  */
@@ -68,6 +73,15 @@ public class ExamController extends BaseController {
         return examService.listByUserId(userId);
     }
 
+    @GetMapping("/template")
+    public void getImportTemplate() {
+        String resourcePath = PathUtil.getResoucePath("importtemplates/examImportTemplate.xlsx");
+        if (Objects.isNull(resourcePath)){
+            throw new StatusException("未找到模板路径");
+        }
+        exportFile("学校导入模板.xlsx", new File(resourcePath));
+    }
+
     /**
      * 删除考试批次
      *
@@ -78,4 +92,9 @@ public class ExamController extends BaseController {
     public void delete(@RequestAttribute PmSession accessEntity, @RequestParam Long examId) {
         examService.deleteExam(examId);
     }
+
+//    @RequestMapping(value = "/e", method = RequestMethod.POST)
+//    public void delete(@RequestAttribute PmSession accessEntity, @RequestParam Long examId) {
+//        examService.deleteExam(examId);
+//    }
 }

+ 19 - 13
src/main/java/cn/com/qmth/print/manage/controller/ExamStudentController.java

@@ -1,24 +1,19 @@
 package cn.com.qmth.print.manage.controller;
 
-import java.io.File;
-import java.io.IOException;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-
 import cn.com.qmth.print.manage.service.ExamStudentService;
 import cn.com.qmth.print.manage.service.query.ExamStudentQuery;
 import cn.com.qmth.print.manage.utils.PathUtil;
-
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
 import com.qmth.boot.api.constant.ApiConstant;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
 
 /**
  * @Date: 2021/11/16.
@@ -41,6 +36,17 @@ public class ExamStudentController extends BaseController {
         return examStudentService.analyzeZipAndImportStudents(examId, file);
     }
 
+    /**
+     * 导出云阅卷考生数据
+     * @param response response
+     * @param examId 考试id
+     * @throws IOException 异常
+     */
+    @RequestMapping(value = "/export", method = RequestMethod.POST)
+    public void exportExamStudent(HttpServletResponse response, @RequestParam("examId") Long examId) throws IOException {
+        examStudentService.exportExamStudent(response, examId);
+    }
+
     /**
      * 分页考生详情
      *

+ 3 - 1
src/main/java/cn/com/qmth/print/manage/controller/SysController.java

@@ -68,6 +68,7 @@ public class SysController extends BaseController {
      * @param creatorDpr   是否启用当前人为用户创建人权限
      * @return 用户集合
      */
+    @Aac(strict = BOOL.FALSE, auth = BOOL.TRUE)
     @ApiOperation(value = "公共接口-查询用户数据")
     @RequestMapping(value = "/common/find_user_list", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "查询成功", response = CommonQueryVo.class)})
@@ -82,7 +83,7 @@ public class SysController extends BaseController {
         if (Objects.nonNull(role)) {
             queryWrapper.lambda().eq(UserEntity::getRole, role);
         }
-        if (creatorDpr) {
+        if (Objects.nonNull(creatorDpr) && creatorDpr) {
             Long userId = getAccessUserId(accessEntity);
             queryWrapper.lambda().eq(UserEntity::getCreatorId, userId);
         }
@@ -108,6 +109,7 @@ public class SysController extends BaseController {
      * @param userId 印点负责人id
      * @return 考试批次集合
      */
+    @Aac(strict = BOOL.FALSE, auth = BOOL.TRUE)
     @ApiOperation(value = "公共接口-查询考试批次数据")
     @RequestMapping(value = "/common/find_exam_list", method = RequestMethod.POST)
     @ApiResponses({@ApiResponse(code = 200, message = "查询成功", response = CommonQueryVo.class)})

+ 9 - 2
src/main/java/cn/com/qmth/print/manage/controller/UserController.java

@@ -61,13 +61,20 @@ public class UserController extends BaseController {
      * @return
      */
     @RequestMapping(value = "/save", method = RequestMethod.POST)
-    public Object save(@RequestAttribute PmSession accessEntity, @RequestBody UserEntity user, @RequestParam(required = false) MultipartFile file) throws IOException {
+    public Object save(@RequestAttribute PmSession accessEntity,
+                       @RequestParam Long id,
+                       @RequestParam String loginName,
+                       @RequestParam String name,
+                       @RequestParam String password,
+                       @RequestParam boolean enable,
+                       @RequestParam RoleMeta role,
+                       @RequestParam(required = false) MultipartFile file) throws IOException {
         Long userId = getAccessUserId(accessEntity);
         UserEntity requestUser = userService.getById(userId);
         if (Objects.isNull(requestUser)) {
             throw new StatusException("当前用户不存在");
         }
-        return userService.save(requestUser, user, file);
+        return userService.save(requestUser, id, loginName, name, role, password, enable, file);
     }
 
     @ApiOperation(value = "修改用户密码", notes = "修改密码")

+ 11 - 1
src/main/java/cn/com/qmth/print/manage/dao/ExamStudentDao.java

@@ -1,9 +1,19 @@
 package cn.com.qmth.print.manage.dao;
 
+import cn.com.qmth.print.manage.dto.ExamStudentExportDTO;
 import cn.com.qmth.print.manage.entity.ExamStudentEntity;
-
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 public interface ExamStudentDao extends BaseMapper<ExamStudentEntity> {
 
+    /**
+     * 查询云阅卷考生导出数据
+     *
+     * @param examId 考试id
+     * @return 考生导出dto
+     */
+    List<ExamStudentExportDTO> findExportData(@Param("examId") Long examId);
 }

+ 113 - 68
src/main/java/cn/com/qmth/print/manage/dto/ExamStudentExportDTO.java

@@ -1,6 +1,6 @@
 package cn.com.qmth.print.manage.dto;
 
-import cn.com.qmth.print.manage.utils.excel.ExcelProperty;
+import cn.com.qmth.print.manage.utils.excel.ExcelField;
 
 import java.io.Serializable;
 
@@ -11,65 +11,110 @@ import java.io.Serializable;
  */
 public class ExamStudentExportDTO implements Serializable {
 
-    @ExcelProperty(index = 1, name = "课程代码*", type = 2)
-    private String courseCode;
-
-    @ExcelProperty(index = 2, name = "课程名称*", type = 2)
-    private String courseName;
-
-    @ExcelProperty(index = 3, name = "准考证号*", type = 2)
+    /**
+     * 科目代码
+     */
+    @ExcelField(title = "课程代码*", align = 2, sort = 1)
+    private String subjectCode;
+
+    /**
+     * 科目名称
+     */
+    @ExcelField(title = "课程名称*", align = 2, sort = 2)
+    private String subjectName;
+
+    /**
+     * 准考证号
+     */
+    @ExcelField(title = "准考证号*", align = 2, sort = 3)
     private String examNumber;
 
-    @ExcelProperty(index = 4, name = "学号*", type = 2)
+    /**
+     * 学号
+     */
+    @ExcelField(title = "学号*", align = 2, sort = 4)
     private String studentCode;
 
-    @ExcelProperty(index = 5, name = "姓名*", type = 2)
-    private String studentName;
-
-    @ExcelProperty(index = 6, name = "签到表编号", type = 2)
-    private String signCode;
-
-    @ExcelProperty(index = 7, name = "考点信息", type = 2)
+    /**
+     * 姓名
+     */
+    @ExcelField(title = "姓名*", align = 2, sort = 5)
+    private String name;
+
+    /**
+     * 试卷袋签到表编号
+     */
+    @ExcelField(title = "签到表编号", align = 2, sort = 6)
+    private String packageCode;
+
+    /**
+     * 考生考点信息
+     */
+    @ExcelField(title = "考点信息", align = 2, sort = 7)
     private String examSite;
 
-    @ExcelProperty(index = 8, name = "考场信息", type = 2)
+    /**
+     * 考生考场信息
+     */
+    @ExcelField(title = "考场信息", align = 2, sort = 8)
     private String examRoom;
 
-    @ExcelProperty(index = 9, name = "考生备注信息", type = 2)
-    private String examStudentRemark;
-
-    @ExcelProperty(index = 10, name = "层次", type = 2)
-    private String level;
-
-    @ExcelProperty(index = 11, name = "专业类型", type = 2)
-    private String major;
-
-    @ExcelProperty(index = 12, name = "学院*", type = 2)
+    /**
+     * 考生备注信息
+     */
+    @ExcelField(title = "考生备注信息", align = 2, sort = 9)
+    private String remark;
+
+    /**
+     * 科目层次信息
+     */
+    @ExcelField(title = "层次", align = 2, sort = 10)
+    private String subjectLevel;
+
+    /**
+     * 专业类型信息
+     */
+    @ExcelField(title = "专业类型", align = 2, sort = 11)
+    private String subjectCategory;
+
+    /**
+     * 学院
+     */
+    @ExcelField(title = "学院*", align = 2, sort = 12)
     private String college;
 
-    @ExcelProperty(index = 13, name = "班级*", type = 2)
-    private String clazz;
+    /**
+     * 班级
+     */
+    @ExcelField(title = "班级*", align = 2, sort = 13)
+    private String className;
 
-    @ExcelProperty(index = 14, name = "任课老师*", type = 2)
+    /**
+     * 任课老师
+     */
+    @ExcelField(title = "任课老师*", align = 2, sort = 14)
     private String teacher;
 
-    @ExcelProperty(index = 15, name = "科目备注信息", type = 2)
-    private String courseRemark;
+    /**
+     * 科目备注信息
+     */
+    @ExcelField(title = "科目备注信息", align = 2, sort = 15)
+    private String subjectRemark;
 
-    public String getCourseCode() {
-        return courseCode;
+    public String getSubjectCode() {
+        return subjectCode;
     }
 
-    public void setCourseCode(String courseCode) {
-        this.courseCode = courseCode;
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
     }
 
-    public String getCourseName() {
-        return courseName;
+    public String getSubjectName() {
+        return subjectName;
     }
 
-    public void setCourseName(String courseName) {
-        this.courseName = courseName;
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
     }
 
     public String getExamNumber() {
@@ -88,20 +133,20 @@ public class ExamStudentExportDTO implements Serializable {
         this.studentCode = studentCode;
     }
 
-    public String getStudentName() {
-        return studentName;
+    public String getName() {
+        return name;
     }
 
-    public void setStudentName(String studentName) {
-        this.studentName = studentName;
+    public void setName(String name) {
+        this.name = name;
     }
 
-    public String getSignCode() {
-        return signCode;
+    public String getPackageCode() {
+        return packageCode;
     }
 
-    public void setSignCode(String signCode) {
-        this.signCode = signCode;
+    public void setPackageCode(String packageCode) {
+        this.packageCode = packageCode;
     }
 
     public String getExamSite() {
@@ -120,28 +165,28 @@ public class ExamStudentExportDTO implements Serializable {
         this.examRoom = examRoom;
     }
 
-    public String getExamStudentRemark() {
-        return examStudentRemark;
+    public String getRemark() {
+        return remark;
     }
 
-    public void setExamStudentRemark(String examStudentRemark) {
-        this.examStudentRemark = examStudentRemark;
+    public void setRemark(String remark) {
+        this.remark = remark;
     }
 
-    public String getLevel() {
-        return level;
+    public String getSubjectLevel() {
+        return subjectLevel;
     }
 
-    public void setLevel(String level) {
-        this.level = level;
+    public void setSubjectLevel(String subjectLevel) {
+        this.subjectLevel = subjectLevel;
     }
 
-    public String getMajor() {
-        return major;
+    public String getSubjectCategory() {
+        return subjectCategory;
     }
 
-    public void setMajor(String major) {
-        this.major = major;
+    public void setSubjectCategory(String subjectCategory) {
+        this.subjectCategory = subjectCategory;
     }
 
     public String getCollege() {
@@ -152,12 +197,12 @@ public class ExamStudentExportDTO implements Serializable {
         this.college = college;
     }
 
-    public String getClazz() {
-        return clazz;
+    public String getClassName() {
+        return className;
     }
 
-    public void setClazz(String clazz) {
-        this.clazz = clazz;
+    public void setClassName(String className) {
+        this.className = className;
     }
 
     public String getTeacher() {
@@ -168,11 +213,11 @@ public class ExamStudentExportDTO implements Serializable {
         this.teacher = teacher;
     }
 
-    public String getCourseRemark() {
-        return courseRemark;
+    public String getSubjectRemark() {
+        return subjectRemark;
     }
 
-    public void setCourseRemark(String courseRemark) {
-        this.courseRemark = courseRemark;
+    public void setSubjectRemark(String subjectRemark) {
+        this.subjectRemark = subjectRemark;
     }
 }

+ 10 - 0
src/main/java/cn/com/qmth/print/manage/entity/ExamStudentEntity.java

@@ -18,6 +18,8 @@ public class ExamStudentEntity extends AuditingWithoutIdEntity {
 
     private String courseCode;
 
+    private String courseName;
+
     private String name;
 
     private String studentCode;
@@ -61,6 +63,14 @@ public class ExamStudentEntity extends AuditingWithoutIdEntity {
         this.courseCode = courseCode;
     }
 
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
     public String getName() {
         return name;
     }

+ 11 - 0
src/main/java/cn/com/qmth/print/manage/service/ExamStudentService.java

@@ -6,8 +6,10 @@ import cn.com.qmth.print.manage.enums.GroupType;
 import cn.com.qmth.print.manage.service.query.ExamStudentQuery;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.List;
 
@@ -25,10 +27,19 @@ public interface ExamStudentService extends IService<ExamStudentEntity> {
 
     /**
      * 解析Zip并导入考生
+     *
      * @param examId 考试id
      * @return 信息
      */
     Object analyzeZipAndImportStudents(Long examId, MultipartFile file) throws IOException;
 
+    /**
+     * 考生数据导出
+     *
+     * @param response response
+     * @param examId   考试id
+     */
+    void exportExamStudent(HttpServletResponse response, Long examId) throws IOException;
+
     void deleteByExamId(Long examId);
 }

+ 2 - 1
src/main/java/cn/com/qmth/print/manage/service/UserService.java

@@ -1,6 +1,7 @@
 package cn.com.qmth.print.manage.service;
 
 import cn.com.qmth.print.manage.entity.UserEntity;
+import cn.com.qmth.print.manage.enums.RoleMeta;
 import cn.com.qmth.print.manage.service.query.UserQuery;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -20,7 +21,7 @@ public interface UserService extends IService<UserEntity> {
      * @param file        批次导入文件
      * @return 用户保存结果
      */
-    boolean save(UserEntity requestUser, UserEntity user, MultipartFile file) throws IOException;
+    boolean save(UserEntity requestUser, Long id, String loginName, String name, RoleMeta role, String password, boolean enable, MultipartFile file) throws IOException;
 
     IPage<UserEntity> pageQuery(UserQuery query);
 

+ 13 - 1
src/main/java/cn/com/qmth/print/manage/service/impl/ExamStudentServiceImpl.java

@@ -2,6 +2,7 @@ package cn.com.qmth.print.manage.service.impl;
 
 import cn.com.qmth.print.manage.dao.ExamDao;
 import cn.com.qmth.print.manage.dao.ExamStudentDao;
+import cn.com.qmth.print.manage.dto.ExamStudentExportDTO;
 import cn.com.qmth.print.manage.dto.ExamStudentImportDTO;
 import cn.com.qmth.print.manage.dto.StudentDTO;
 import cn.com.qmth.print.manage.entity.CheckRecordEntity;
@@ -16,6 +17,7 @@ import cn.com.qmth.print.manage.service.query.ExamStudentQuery;
 import cn.com.qmth.print.manage.utils.Zip4jUtil;
 import cn.com.qmth.print.manage.utils.excel.ExcelError;
 import cn.com.qmth.print.manage.utils.excel.ExcelReader;
+import cn.com.qmth.print.manage.utils.excel.ExportExcel;
 import cn.hutool.core.io.FileUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -32,6 +34,7 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
@@ -53,6 +56,7 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentDao, ExamStud
 
     @Override
     public ExamStudentEntity findByExamIdAndExamNumber(Long examId, String examNumber) {
+        // TODO: 2023/10/20 校验更改逻辑 
         if (examId == null) {
             throw new StatusException("examId不能为空");
         }
@@ -199,7 +203,8 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentDao, ExamStud
                 examStudentEntity.setExamNumber(dto.getExamNumber());
                 examStudentEntity.setStudentCode(dto.getStudentCode());
                 examStudentEntity.setName(dto.getStudentName());
-                examStudentEntity.setCourseCode(dto.getCourseCode().concat("_").concat(dto.getCourseName()));
+                examStudentEntity.setCourseCode(dto.getCourseCode());
+                examStudentEntity.setCourseName(dto.getCourseName());
                 examStudentEntity.setExamSite(dto.getExamPlaceName());
                 examStudentEntity.setSortNo(sort.getAndIncrement());
                 examStudentEntity.setExamUnit(dto.getExamUnit());
@@ -227,6 +232,13 @@ public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentDao, ExamStud
         return excelErrors;
     }
 
+    @Override
+    public void exportExamStudent(HttpServletResponse response, Long examId) throws IOException {
+        List<ExamStudentExportDTO> examStudentExportDTOList = this.baseMapper.findExportData(examId);
+        new ExportExcel("考生数据", ExamStudentExportDTO.class).setDataList(examStudentExportDTOList).write(response, "")
+                .dispose();
+    }
+
     @Override
     public void deleteByExamId(Long examId) {
         UpdateWrapper<ExamStudentEntity> updateWrapper = new UpdateWrapper<>();

+ 21 - 13
src/main/java/cn/com/qmth/print/manage/service/impl/UserServiceImpl.java

@@ -32,22 +32,21 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public boolean save(UserEntity requestUser, UserEntity user, MultipartFile file) throws IOException {
+    public boolean save(UserEntity requestUser, Long id, String loginName, String name, RoleMeta role, String password, boolean enable, MultipartFile file) throws IOException {
+        UserEntity user = new UserEntity();
         Long requestUserId = requestUser.getId();
-        if (user.getId() != null) {
-            UserEntity userEntity = this.getById(user.getId());
+        if (id != null) {
+            user.setId(id);
+            UserEntity userEntity = this.getById(id);
             if (userEntity == null) {
                 throw new StatusException("用户不存在");
             }
-            if (!user.getLoginName().equals(userEntity.getLoginName())) {
+            if (!loginName.equals(userEntity.getLoginName())) {
                 throw new StatusException("用户名不能修改");
             }
         }
-        if (StringUtils.isBlank(user.getName())) {
-            user.setName(user.getLoginName());
-        }
+
         RoleMeta requestUserRole = requestUser.getRole();
-        RoleMeta role = user.getRole();
         switch (requestUserRole) {
             case ADMIN:
                 if (!RoleMeta.PRINT_LEADER.equals(role)) {
@@ -63,16 +62,25 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements
                 throw new StatusException(String.format("只有[%s][%s]角色的用户可以编辑用户", RoleMeta.ADMIN, RoleMeta.PRINT_LEADER));
         }
 
-
+        user.setLoginName(loginName);
+        user.setRole(role);
+        if (StringUtils.isBlank(name)) {
+            user.setName(loginName);
+        } else {
+            user.setName(name);
+        }
         user.setCreatorId(requestUserId);
         user.setCreateTime(new Date());
         user.setUpdaterId(requestUserId);
         user.setUpdateTime(new Date());
+        if (password != null && password.length() > 0) {
+            user.setPassword(password);
+        }
+        user.setEnable(enable);
         boolean result = this.saveOrUpdate(user);
-        Long userId = user.getId();
         if (RoleMeta.ADMIN.equals(requestUser.getRole()) && Objects.nonNull(file)) {
             // 如果是管理员且包含导入文件-处理导入批次逻辑
-            examService.importExams(requestUser, userId, file);
+            examService.importExams(requestUser, id, file);
         }
         return result;
     }
@@ -80,8 +88,8 @@ public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements
     @Override
     public IPage<UserEntity> pageQuery(UserQuery query) {
         QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
-        if (StringUtils.isNotEmpty(query.getLoginName())) {
-            queryWrapper.lambda().like(UserEntity::getLoginName, query.getLoginName() + "%");
+        if (query.getUserId() != null && query.getUserId() > 0) {
+            queryWrapper.lambda().eq(UserEntity::getId, query.getUserId());
         }
         if (query.getRole() != null) {
             queryWrapper.lambda().eq(UserEntity::getRole, query.getRole());

+ 5 - 5
src/main/java/cn/com/qmth/print/manage/service/query/UserQuery.java

@@ -14,19 +14,19 @@ public class UserQuery extends BaseQuery<UserEntity> {
 
     private static final long serialVersionUID = 3260496142491163981L;
 
-    private String loginName;
+    private Long userId;
 
     private RoleMeta role;
 
     @NotNull
     private boolean enable;
 
-    public String getLoginName() {
-        return loginName;
+    public Long getUserId() {
+        return userId;
     }
 
-    public void setLoginName(String loginName) {
-        this.loginName = loginName;
+    public void setUserId(Long userId) {
+        this.userId = userId;
     }
 
     public RoleMeta getRole() {

+ 59 - 0
src/main/java/cn/com/qmth/print/manage/utils/excel/ExcelField.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.print.manage.utils.excel;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @Description: Excel注解定义
+ * @Author: CaoZixuan
+ * @Date: 2023-10-20
+ */
+
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelField {
+
+    /**
+     * 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”)
+     */
+    String value() default "";
+
+    /**
+     * 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效)
+     */
+    String title();
+
+    /**
+     * 字段类型(0:导出导入;1:仅导出;2:仅导入)
+     */
+    int type() default 0;
+
+    /**
+     * 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右)
+     *
+     * 备注:Integer/Long类型设置居右对齐(align=3)
+     */
+    int align() default 0;
+
+    /**
+     * 导出字段字段排序(升序)
+     */
+    int sort() default 0;
+
+    /**
+     * 如果是字典类型,请设置字典的type值
+     */
+    String dictType() default "";
+
+    /**
+     * 反射类型
+     */
+    Class<?> fieldType() default Class.class;
+
+    /**
+     * 字段归属组(根据分组导出导入)
+     */
+    int[] groups() default {};
+}

+ 10 - 9
src/main/java/cn/com/qmth/print/manage/utils/excel/ExcelReader.java

@@ -1,5 +1,6 @@
 package cn.com.qmth.print.manage.utils.excel;
 
+import org.apache.poi.hssf.usermodel.HSSFCell;
 import org.apache.poi.hssf.usermodel.HSSFDateUtil;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.ss.usermodel.*;
@@ -131,9 +132,9 @@ public class ExcelReader extends ExcelUtils {
 	 */
 	public static String analyzeExcelCellValue(Cell cell) {
 		String cellValue;
-		CellType cellType = cell.getCellTypeEnum();
-		switch (cellType) {
-			case NUMERIC:
+
+		switch (cell.getCellType()) {
+			case HSSFCell.CELL_TYPE_NUMERIC:
 				if (HSSFDateUtil.isCellDateFormatted(cell)) {
 					SimpleDateFormat sdf;
 					// 验证short值
@@ -149,23 +150,23 @@ public class ExcelReader extends ExcelUtils {
 					Date date = cell.getDateCellValue();
 					cellValue = sdf.format(date);
 				} else {//处理数值格式
-					cell.setCellType(CellType.STRING);
+					cell.setCellType(HSSFCell.CELL_TYPE_STRING);
 					cellValue = String.valueOf(cell.getRichStringCellValue().getString());
 				}
 				break;
-			case STRING:
+			case HSSFCell.CELL_TYPE_STRING:
 				cellValue = String.valueOf(cell.getStringCellValue());
 				break;
-			case BOOLEAN:
+			case HSSFCell.CELL_TYPE_FORMULA:
 				cellValue = String.valueOf(cell.getBooleanCellValue());
 				break;
-			case FORMULA:
+			case HSSFCell.CELL_TYPE_BLANK:
 				cellValue = String.valueOf(cell.getCellFormula());
 				break;
-			case BLANK:
+			case HSSFCell.CELL_TYPE_BOOLEAN:
 				cellValue = null;
 				break;
-			case ERROR:
+			case HSSFCell.CELL_TYPE_ERROR:
 				cellValue = "非法字符";
 				break;
 			default:

+ 456 - 0
src/main/java/cn/com/qmth/print/manage/utils/excel/ExportExcel.java

@@ -0,0 +1,456 @@
+package cn.com.qmth.print.manage.utils.excel;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URLEncoder;
+import java.util.*;
+
+/**
+ * 导出Excel文件(导出“XLSX”格式,支持大数据量导出   @see org.apache.poi.ss.SpreadsheetVersion)
+ * @author XiongKaiJun
+ * @version 2016-06-12
+ */
+public class ExportExcel {
+
+    private static Logger log = LoggerFactory.getLogger(ExportExcel.class);
+
+    /**
+     * 工作薄对象
+     */
+    private SXSSFWorkbook wb;
+
+    /**
+     * 工作表对象
+     */
+    private Sheet sheet;
+
+    /**
+     * 样式列表
+     */
+    private Map<String, CellStyle> styles;
+
+    /**
+     * 当前行号
+     */
+    private int rownum;
+
+    /**
+     * 注解列表(Object[]{ ExcelField, Field/Method })
+     */
+    List<Object[]> annotationList = Lists.newArrayList();
+
+    /**
+     * 构造函数
+     * @param title 表格标题,传“空值”,表示无标题
+     * @param cls 实体对象,通过annotation.ExportField获取标题
+     */
+    public ExportExcel(String title, Class<?> cls){
+        this(title, cls, 1);
+    }
+
+    /**
+     * 构造函数
+     * @param title 表格标题,传“空值”,表示无标题
+     * @param cls 实体对象,通过annotation.ExportField获取标题
+     * @param type 导出类型(1:导出数据;2:导出模板)
+     * @param groups 导入分组
+     */
+    public ExportExcel(String title, Class<?> cls, int type, int... groups){
+        // Get annotation field
+        Field[] fs = cls.getDeclaredFields();
+        for (Field f : fs){
+            ExcelField ef = f.getAnnotation(ExcelField.class);
+            if (ef != null && (ef.type()==0 || ef.type()==type)){
+                if (groups!=null && groups.length>0){
+                    boolean inGroup = false;
+                    for (int g : groups){
+                        if (inGroup){
+                            break;
+                        }
+                        for (int efg : ef.groups()){
+                            if (g == efg){
+                                inGroup = true;
+                                annotationList.add(new Object[]{ef, f});
+                                break;
+                            }
+                        }
+                    }
+                }else{
+                    annotationList.add(new Object[]{ef, f});
+                }
+            }
+        }
+        // Get annotation method
+        Method[] ms = cls.getDeclaredMethods();
+        for (Method m : ms){
+            ExcelField ef = m.getAnnotation(ExcelField.class);
+            if (ef != null && (ef.type()==0 || ef.type()==type)){
+                if (groups!=null && groups.length>0){
+                    boolean inGroup = false;
+                    for (int g : groups){
+                        if (inGroup){
+                            break;
+                        }
+                        for (int efg : ef.groups()){
+                            if (g == efg){
+                                inGroup = true;
+                                annotationList.add(new Object[]{ef, m});
+                                break;
+                            }
+                        }
+                    }
+                }else{
+                    annotationList.add(new Object[]{ef, m});
+                }
+            }
+        }
+        // Field sorting
+        Collections.sort(annotationList, new Comparator<Object[]>() {
+            public int compare(Object[] o1, Object[] o2) {
+                return new Integer(((ExcelField)o1[0]).sort()).compareTo(
+                        new Integer(((ExcelField)o2[0]).sort()));
+            };
+        });
+        // Initialize
+        List<String> headerList = Lists.newArrayList();
+        for (Object[] os : annotationList){
+            String t = ((ExcelField)os[0]).title();
+            // 如果是导出,则去掉注释
+            if (type==1){
+                String[] ss = StringUtils.split(t, "**", 2);
+                if (ss.length==2){
+                    t = ss[0];
+                }
+            }
+            headerList.add(t);
+        }
+        initialize(title, headerList);
+    }
+
+    /**
+     * 构造函数
+     * @param title 表格标题,传“空值”,表示无标题
+     * @param headers 表头数组
+     */
+    public ExportExcel(String title, String[] headers) {
+        initialize(title, Lists.newArrayList(headers));
+    }
+
+    /**
+     * 构造函数
+     * @param title 表格标题,传“空值”,表示无标题
+     * @param headerList 表头列表
+     */
+    public ExportExcel(String title, List<String> headerList) {
+        initialize(title, headerList);
+    }
+
+    /**
+     * 初始化函数
+     * @param title 表格标题,传“空值”,表示无标题
+     * @param headerList 表头列表
+     */
+    private void initialize(String title, List<String> headerList) {
+        this.wb = new SXSSFWorkbook(500);
+        this.sheet = wb.createSheet("Export");
+        this.styles = createStyles(wb);
+        // Create title
+        if (StringUtils.isNotBlank(title)){
+            Row titleRow = sheet.createRow(rownum++);
+            titleRow.setHeightInPoints(30);
+            Cell titleCell = titleRow.createCell(0);
+            titleCell.setCellStyle(styles.get("title"));
+            titleCell.setCellValue(title);
+            sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(),
+                    titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1));
+        }
+        // Create header
+        if (headerList == null){
+            throw new RuntimeException("headerList not null!");
+        }
+        Row headerRow = sheet.createRow(rownum++);
+        headerRow.setHeightInPoints(16);
+        for (int i = 0; i < headerList.size(); i++) {
+            Cell cell = headerRow.createCell(i);
+            cell.setCellStyle(styles.get("header"));
+            String[] ss = StringUtils.split(headerList.get(i), "**", 2);
+            if (ss.length==2){
+                cell.setCellValue(ss[0]);
+                Comment comment = this.sheet.createDrawingPatriarch().createCellComment(
+                        new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6));
+                comment.setString(new XSSFRichTextString(ss[1]));
+                cell.setCellComment(comment);
+            }else{
+                cell.setCellValue(headerList.get(i));
+            }
+            sheet.autoSizeColumn(i);
+        }
+        for (int i = 0; i < headerList.size(); i++) {
+            int colWidth = sheet.getColumnWidth(i)*2;
+            sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth);
+        }
+        log.debug("Initialize success.");
+    }
+
+    /**
+     * 创建表格样式
+     * @param wb 工作薄对象
+     * @return 样式列表
+     */
+    private Map<String, CellStyle> createStyles(Workbook wb) {
+        Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
+
+        CellStyle style = wb.createCellStyle();
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        Font titleFont = wb.createFont();
+        titleFont.setFontName("Arial");
+        titleFont.setFontHeightInPoints((short) 16);
+        titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
+        style.setFont(titleFont);
+        styles.put("title", style);
+
+        style = wb.createCellStyle();
+        style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
+        style.setBorderRight(CellStyle.BORDER_THIN);
+        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderLeft(CellStyle.BORDER_THIN);
+        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderTop(CellStyle.BORDER_THIN);
+        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setBorderBottom(CellStyle.BORDER_THIN);
+        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        Font dataFont = wb.createFont();
+        dataFont.setFontName("Arial");
+        dataFont.setFontHeightInPoints((short) 10);
+        style.setFont(dataFont);
+        styles.put("data", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(CellStyle.ALIGN_LEFT);
+        styles.put("data1", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        styles.put("data2", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+        style.setAlignment(CellStyle.ALIGN_RIGHT);
+        styles.put("data3", style);
+
+        style = wb.createCellStyle();
+        style.cloneStyleFrom(styles.get("data"));
+//		style.setWrapText(true);
+        style.setAlignment(CellStyle.ALIGN_CENTER);
+        style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+        style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+        Font headerFont = wb.createFont();
+        headerFont.setFontName("Arial");
+        headerFont.setFontHeightInPoints((short) 10);
+        headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
+        headerFont.setColor(IndexedColors.WHITE.getIndex());
+        style.setFont(headerFont);
+        styles.put("header", style);
+
+        return styles;
+    }
+
+    /**
+     * 添加一行
+     * @return 行对象
+     */
+    public Row addRow(){
+        return sheet.createRow(rownum++);
+    }
+
+
+    /**
+     * 添加一个单元格
+     * @param row 添加的行
+     * @param column 添加列号
+     * @param val 添加值
+     * @return 单元格对象
+     */
+    public Cell addCell(Row row, int column, Object val){
+        return this.addCell(row, column, val, 0, Class.class);
+    }
+
+    /**
+     * 添加一个单元格
+     * @param row 添加的行
+     * @param column 添加列号
+     * @param val 添加值
+     * @param align 对齐方式(1:靠左;2:居中;3:靠右)
+     * @return 单元格对象
+     */
+    public Cell addCell(Row row, int column, Object val, int align, Class<?> fieldType){
+        Cell cell = row.createCell(column);
+        CellStyle style = styles.get("data"+(align>=1&&align<=3?align:""));
+        try {
+            if (val == null){
+                cell.setCellValue("");
+            } else if (val instanceof String) {
+                cell.setCellValue((String) val);
+            } else if (val instanceof Integer) {
+                cell.setCellValue((Integer) val);
+            } else if (val instanceof Long) {
+                cell.setCellValue((Long) val);
+            } else if (val instanceof Double) {
+                cell.setCellValue((Double) val);
+            } else if (val instanceof Float) {
+                cell.setCellValue((Float) val);
+            } else if (val instanceof Date) {
+                DataFormat format = wb.createDataFormat();
+                style.setDataFormat(format.getFormat("yyyy-MM-dd"));
+                cell.setCellValue((Date) val);
+            } else {
+                if (fieldType != Class.class){
+                    cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val));
+                }else{
+                    cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
+                            "fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val));
+                }
+            }
+        } catch (Exception ex) {
+            log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString());
+            cell.setCellValue(val.toString());
+        }
+        cell.setCellStyle(style);
+        return cell;
+    }
+
+    /**
+     * 添加数据(通过annotation.ExportField添加数据)
+     * @return list 数据列表
+     */
+    public <E> ExportExcel setDataList(List<E> list){
+        for (E e : list){
+            int colunm = 0;
+            Row row = this.addRow();
+            StringBuilder sb = new StringBuilder();
+            for (Object[] os : annotationList){
+                ExcelField ef = (ExcelField)os[0];
+                Object val = null;
+                // Get entity value
+                try{
+                    if (StringUtils.isNotBlank(ef.value())){
+                        val = Reflections.invokeGetter(e, ef.value());
+                    }else{
+                        if (os[1] instanceof Field){
+                            val = Reflections.invokeGetter(e, ((Field)os[1]).getName());
+                        }else if (os[1] instanceof Method){
+                            val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {});
+                        }
+                    }
+                    // If is dict, get dict label
+					/*if (StringUtils.isNotBlank(ef.dictType())){
+						val = DictUtils.getDictLabel(val==null?"":val.toString(), ef.dictType(), "");
+					}*/
+                }catch(Exception ex) {
+                    // Failure to ignore
+                    log.info(ex.toString());
+                    val = "";
+                }
+                this.addCell(row, colunm++, val, ef.align(), ef.fieldType());
+                sb.append(val + ", ");
+            }
+            log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString());
+        }
+        return this;
+    }
+
+    /**
+     * 输出数据流
+     * @param os 输出数据流
+     */
+    public ExportExcel write(OutputStream os) throws IOException{
+        wb.write(os);
+        return this;
+    }
+
+    /**
+     * 输出到客户端
+     * @param fileName 输出文件名
+     */
+    public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{
+        response.reset();
+        response.setContentType("application/octet-stream; charset=utf-8");
+        response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "UTF-8"));
+        write(response.getOutputStream());
+        return this;
+    }
+
+    /**
+     * 输出到文件
+     * @param fileName 输出文件名
+     */
+    public ExportExcel writeFile(String name) throws FileNotFoundException, IOException{
+        FileOutputStream os = new FileOutputStream(name);
+        this.write(os);
+        return this;
+    }
+
+    /**
+     * 清理临时文件
+     */
+    public ExportExcel dispose(){
+        wb.dispose();
+        return this;
+    }
+
+//	/**
+//	 * 导出测试
+//	 */
+//	public static void main(String[] args) throws Throwable {
+//
+//		List<String> headerList = Lists.newArrayList();
+//		for (int i = 1; i <= 10; i++) {
+//			headerList.add("表头"+i);
+//		}
+//
+//		List<String> dataRowList = Lists.newArrayList();
+//		for (int i = 1; i <= headerList.size(); i++) {
+//			dataRowList.add("数据"+i);
+//		}
+//
+//		List<List<String>> dataList = Lists.newArrayList();
+//		for (int i = 1; i <=1000000; i++) {
+//			dataList.add(dataRowList);
+//		}
+//
+//		ExportExcel ee = new ExportExcel("表格标题", headerList);
+//
+//		for (int i = 0; i < dataList.size(); i++) {
+//			Row row = ee.addRow();
+//			for (int j = 0; j < dataList.get(i).size(); j++) {
+//				ee.addCell(row, j, dataList.get(i).get(j));
+//			}
+//		}
+//
+//		ee.writeFile("target/export.xlsx");
+//
+//		ee.dispose();
+//
+//		log.debug("Export success.");
+//
+//	}
+
+}

+ 296 - 0
src/main/java/cn/com/qmth/print/manage/utils/excel/Reflections.java

@@ -0,0 +1,296 @@
+package cn.com.qmth.print.manage.utils.excel;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+
+@SuppressWarnings("rawtypes")
+public class Reflections {
+
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    private static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+    private static Logger logger = LoggerFactory.getLogger(Reflections.class);
+
+    /**
+     * 调用Getter方法.
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static Object invokeGetter(Object obj, String propertyName) {
+        Object object = obj;
+        for (String name : StringUtils.split(propertyName, ".")){
+            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+            object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+        }
+        return object;
+    }
+
+    /**
+     * 调用Setter方法, 仅匹配方法名。
+     * 支持多级,如:对象名.对象名.方法
+     */
+    public static void invokeSetter(Object obj, String propertyName, Object value) {
+        Object object = obj;
+        String[] names = StringUtils.split(propertyName, ".");
+        for (int i=0; i<names.length; i++){
+            if(i<names.length-1){
+                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+                object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
+            }else{
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                invokeMethodByName(object, setterMethodName, new Object[] { value });
+            }
+        }
+    }
+
+    /**
+     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
+     */
+    public static Object getFieldValue(final Object obj, final String fieldName) {
+        Field field = getAccessibleField(obj, fieldName);
+
+        if (field == null) {
+            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
+        }
+
+        Object result = null;
+        try {
+            result = field.get(obj);
+        } catch (IllegalAccessException e) {
+            logger.error("不可能抛出的异常{}", e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
+     */
+    public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
+        Field field = getAccessibleField(obj, fieldName);
+
+        if (field == null) {
+            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
+        }
+
+        try {
+            field.set(obj, value);
+        } catch (IllegalAccessException e) {
+            logger.error("不可能抛出的异常:{}", e.getMessage());
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符.
+     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
+     * 同时匹配方法名+参数类型,
+     */
+    public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
+                                      final Object[] args) {
+        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
+        if (method == null) {
+            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
+        }
+
+        try {
+            return method.invoke(obj, args);
+        } catch (Exception e) {
+            throw convertReflectionExceptionToUnchecked(e);
+        }
+    }
+
+    /**
+     * 直接调用对象方法, 无视private/protected修饰符,
+     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
+     * 只匹配函数名,如果有多个同名函数调用第一个。
+     */
+    public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
+        Method method = getAccessibleMethodByName(obj, methodName);
+        if (method == null) {
+            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
+        }
+
+        try {
+            return method.invoke(obj, args);
+        } catch (Exception e) {
+            throw convertReflectionExceptionToUnchecked(e);
+        }
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
+     *
+     * 如向上转型到Object仍无法找到, 返回null.
+     */
+    public static Field getAccessibleField(final Object obj, final String fieldName) {
+        Validate.notNull(obj, "object can't be null");
+        Validate.notBlank(fieldName, "fieldName can't be blank");
+        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
+            try {
+                Field field = superClass.getDeclaredField(fieldName);
+                makeAccessible(field);
+                return field;
+            } catch (NoSuchFieldException e) {//NOSONAR
+                // Field不在当前类定义,继续向上转型
+                continue;// new add
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 匹配函数名+参数类型。
+     *
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethod(final Object obj, final String methodName,
+                                             final Class<?>... parameterTypes) {
+        Validate.notNull(obj, "object can't be null");
+        Validate.notBlank(methodName, "methodName can't be blank");
+
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
+            try {
+                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
+                makeAccessible(method);
+                return method;
+            } catch (NoSuchMethodException e) {
+                // Method不在当前类定义,继续向上转型
+                continue;// new add
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
+     * 如向上转型到Object仍无法找到, 返回null.
+     * 只匹配函数名。
+     *
+     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
+     */
+    public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
+        Validate.notNull(obj, "object can't be null");
+        Validate.notBlank(methodName, "methodName can't be blank");
+
+        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
+            Method[] methods = searchType.getDeclaredMethods();
+            for (Method method : methods) {
+                if (method.getName().equals(methodName)) {
+                    makeAccessible(method);
+                    return method;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Method method) {
+        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+                && !method.isAccessible()) {
+            method.setAccessible(true);
+        }
+    }
+
+    /**
+     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
+     */
+    public static void makeAccessible(Field field) {
+        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
+                .isFinal(field.getModifiers())) && !field.isAccessible()) {
+            field.setAccessible(true);
+        }
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
+     * 如无法找到, 返回Object.class.
+     * eg.
+     * public UserDao extends HibernateDao<User>
+     *
+     * @param clazz The class to introspect
+     * @return the first generic declaration, or Object.class if cannot be determined
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> Class<T> getClassGenricType(final Class clazz) {
+        return getClassGenricType(clazz, 0);
+    }
+
+    /**
+     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+     * 如无法找到, 返回Object.class.
+     *
+     * 如public UserDao extends HibernateDao<User,Long>
+     *
+     * @param clazz clazz The class to introspect
+     * @param index the Index of the generic ddeclaration,start from 0.
+     * @return the index generic declaration, or Object.class if cannot be determined
+     */
+    public static Class getClassGenricType(final Class clazz, final int index) {
+
+        Type genType = clazz.getGenericSuperclass();
+
+        if (!(genType instanceof ParameterizedType)) {
+            logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
+            return Object.class;
+        }
+
+        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
+
+        if (index >= params.length || index < 0) {
+            logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+                    + params.length);
+            return Object.class;
+        }
+        if (!(params[index] instanceof Class)) {
+            logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
+            return Object.class;
+        }
+
+        return (Class) params[index];
+    }
+
+    public static Class<?> getUserClass(Object instance) {
+        Assert.notNull(instance, "Instance must not be null");
+        Class clazz = instance.getClass();
+        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
+            Class<?> superClass = clazz.getSuperclass();
+            if (superClass != null && !Object.class.equals(superClass)) {
+                return superClass;
+            }
+        }
+        return clazz;
+
+    }
+
+    /**
+     * 将反射时的checked exception转换为unchecked exception.
+     */
+    public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
+        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
+                || e instanceof NoSuchMethodException) {
+            return new IllegalArgumentException(e);
+        } else if (e instanceof InvocationTargetException) {
+            return new RuntimeException(((InvocationTargetException) e).getTargetException());
+        } else if (e instanceof RuntimeException) {
+            return (RuntimeException) e;
+        }
+        return new RuntimeException("Unexpected Checked Exception.", e);
+    }
+}
+

BIN
src/main/resources/importtemplates/examImportTemplate.xlsx


+ 25 - 0
src/main/resources/mapper/ExamStudentDao.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="cn.com.qmth.print.manage.dao.ExamStudentDao">
+
+    <select id="findExportData" resultType="cn.com.qmth.print.manage.dto.ExamStudentExportDTO">
+        SELECT
+            course_code AS subjectCode,
+            course_name AS subjectName,
+            exam_number AS examNumber,
+            student_code AS studentCode,
+            name AS name,
+            exam_site AS examSite,
+            exam_room AS examRoom,
+            '1' AS college,
+            exam_unit AS className,
+            '1' AS teacher
+        FROM
+            pm_exam_student
+        <where>
+            <if test="examId != null">
+                AND exam_id = #{examId}
+            </if>
+        </where>
+    </select>
+</mapper>