ting.yin 2 ani în urmă
părinte
comite
3569f9d73c

+ 404 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkCronService.java

@@ -1,8 +1,22 @@
 package cn.com.qmth.stmms.biz.mark.service.Impl;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -12,23 +26,46 @@ import org.springframework.stereotype.Component;
 
 import cn.com.qmth.stmms.biz.config.service.impl.SystemCache;
 import cn.com.qmth.stmms.biz.exam.model.Exam;
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.model.Marker;
+import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
 import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.exam.service.MarkerService;
+import cn.com.qmth.stmms.biz.file.enums.FormatType;
+import cn.com.qmth.stmms.biz.file.service.FileService;
 import cn.com.qmth.stmms.biz.lock.LockService;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
+import cn.com.qmth.stmms.biz.report.model.ReportSubject;
+import cn.com.qmth.stmms.biz.report.model.ReportSubjectClass;
+import cn.com.qmth.stmms.biz.report.model.ReportSubjectCollege;
+import cn.com.qmth.stmms.biz.report.model.ReportSubjectGroup;
+import cn.com.qmth.stmms.biz.report.model.ReportSubjectQuestion;
+import cn.com.qmth.stmms.biz.report.model.ReportSubjectTeacher;
+import cn.com.qmth.stmms.biz.report.query.ReportSubjectQuery;
+import cn.com.qmth.stmms.biz.report.service.ReportService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectClassService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectCollegeService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectGroupService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectQuestionService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectService;
+import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherService;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.ReportSubjectRangeDTO;
+import cn.com.qmth.stmms.biz.utils.ExportExcel;
 import cn.com.qmth.stmms.biz.utils.TaskLockUtil;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.SubjectiveStatus;
 
+import com.qmth.boot.tools.codec.CodecUtils;
+
 /**
  * 与评卷相关的所有定时任务
  *
@@ -66,6 +103,41 @@ public class MarkCronService {
     @Value("${mark.activeExpireMinute}")
     private long markerActiveExpireMinute;
 
+    @Autowired
+    private FileService fileService;
+
+    @Autowired
+    private ExamQuestionService questionService;
+
+    @Autowired
+    private ReportService reportService;
+
+    @Autowired
+    private ReportSubjectQuestionService reportSubjectQuestionService;
+
+    @Autowired
+    private ReportSubjectClassService reportSubjectClassService;
+
+    @Autowired
+    private ReportSubjectTeacherService reportSubjectTeacherService;
+
+    @Autowired
+    private ReportSubjectCollegeService reportSubjectCollegeService;
+
+    @Autowired
+    private ReportSubjectGroupService reportSubjectGroupService;
+
+    @Autowired
+    private ReportSubjectService reportSubjectService;
+
+    private ReportContext context;
+
+    private Map<String, List<ExamQuestion>> objectiveMap;
+
+    private Map<String, List<ExamQuestion>> subjectiveMap;
+
+    private Map<String, ExamSubject> subjectMap;
+
     /**
      * 自动释放可清除的锁
      */
@@ -281,4 +353,336 @@ public class MarkCronService {
         }
     }
 
+    @Scheduled(fixedDelay = 30 * 60 * 1000, initialDelay = 2 * 60 * 1000)
+    public void updateReport() {
+        log.info("start auto-update report");
+        try {
+            Map<Integer, Set<String>> map = new HashMap<Integer, Set<String>>();
+            List<Integer> examIds = groupService.findExamIdByStatus(MarkStatus.FORMAL);
+            for (Integer examId : examIds) {
+                List<ExamSubject> list = subjectService.list(examId);
+                Set<String> set = new HashSet<>();
+                for (ExamSubject subject : list) {
+                    List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(),
+                            subject.getCode(), MarkStatus.TRIAL);
+                    if (!groups.isEmpty()
+                            && !lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
+                        set.add(subject.getCode());
+                    }
+                }
+                if (!set.isEmpty()) {
+                    map.put(examId, set);
+                }
+            }
+            for (Integer examId : map.keySet()) {
+                subjectiveMap = new HashMap<String, List<ExamQuestion>>();
+                objectiveMap = new HashMap<String, List<ExamQuestion>>();
+                subjectMap = new HashMap<String, ExamSubject>();
+                reportExam(examId, map.get(examId));
+            }
+        } catch (Exception e) {
+            log.error("auto-update report error", e);
+        } finally {
+            log.info("finish auto-update report");
+        }
+    }
+
+    private void reportExam(int examId, Set<String> subjectCodeSet) {
+        Exam exam = examService.findById(examId);
+        if (exam == null || exam.getStatus() != ExamStatus.START) {
+            log.info("report exception for examId=" + examId + ", exam is null or status error");
+            return;
+        }
+        if (subjectCodeSet == null) {
+            subjectCodeSet = new HashSet<String>();
+            List<ExamSubject> list = subjectService.list(exam.getId());
+            for (ExamSubject subject : list) {
+                List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subject.getCode(),
+                        MarkStatus.FORMAL, MarkStatus.TRIAL);
+                if (groups != null && !groups.isEmpty()
+                        && !lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
+                    subjectCodeSet.add(subject.getCode());
+                }
+            }
+        }
+        log.info("start report for examId=" + examId + ", subjectCode count=" + subjectCodeSet.size());
+
+        lockService.trylock(LockType.SCORE_CALCULATE, examId);
+        for (String subjectCode : subjectCodeSet) {
+            // 尝试上锁,失败直接跳过
+            if (!lockService.trylock(LockType.SCORE_CALCULATE, examId, subjectCode)) {
+                log.warn("report locked for examId=" + examId + ", subjectCode=" + subjectCode);
+                continue;
+            }
+            try {
+                log.info("start report for examId=" + examId + ", subjectCode=" + subjectCode);
+                // 删除原有统计数据
+                reportService.deleteData(examId, subjectCode);
+                context = new ReportContext(exam);
+                int pageNumber = 1;
+                int pageSize = 1000;
+                List<ExamStudent> list = studentService.findByExamIdAndSubjectCode(examId, subjectCode, pageNumber,
+                        pageSize);
+                while (list != null && list.size() > 0) {
+                    for (ExamStudent student : list) {
+                        // 统计
+                        statistic(student);
+                    }
+                    pageNumber++;
+                    list = studentService.findByExamIdAndSubjectCode(examId, subjectCode, pageNumber, pageSize);
+                }
+                // 结束统计
+                context.save();
+                // 生成文件
+                reoprtExcel(examId, subjectCode);
+            } catch (Exception e) {
+                log.error("report exception for examId=" + examId + ", subjectCode=" + subjectCode, e);
+            } finally {
+                lockService.unlock(LockType.SCORE_CALCULATE, examId, subjectCode);
+                log.info("finish report for examId=" + examId + ", subjectCode=" + subjectCode);
+            }
+        }
+        lockService.unlock(LockType.SCORE_CALCULATE, examId);
+    }
+
+    private void statistic(ExamStudent student) {
+        if (SubjectiveStatus.MARKED.equals(student.getSubjectiveStatus())
+                || SubjectiveStatus.INSPECTED.equals(student.getSubjectiveStatus())) {
+            student.setSubject(findExamSubject(student.getExamId(), student.getSubjectCode()));
+            student.setObjectiveQuestionList(findQuestionList(student.getExamId(), student.getSubjectCode(),
+                    student.getPaperType(), true));
+            student.setSubjectiveQuestionList(findQuestionList(student.getExamId(), student.getSubjectCode(),
+                    student.getPaperType(), false));
+            context.process(student);
+        }
+    }
+
+    private List<ExamQuestion> findQuestionList(int examId, String subjectCode, String paperType, boolean objective) {
+        if (objective) {
+            String key = subjectCode + "_" + StringUtils.trimToEmpty(paperType);
+            List<ExamQuestion> list = objectiveMap.get(key);
+            if (list == null) {
+                list = questionService.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, true,
+                        paperType);
+                objectiveMap.put(key, list);
+            }
+            return list;
+        } else {
+            List<ExamQuestion> list = subjectiveMap.get(subjectCode);
+            if (list == null) {
+                list = questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false);
+                subjectiveMap.put(subjectCode, list);
+            }
+            return list;
+        }
+    }
+
+    private ExamSubject findExamSubject(int examId, String subjectCode) {
+        ExamSubject subject = subjectMap.get(subjectCode);
+        if (subject == null) {
+            subject = subjectService.find(examId, subjectCode);
+            subjectMap.put(subjectCode, subject);
+        }
+        return subject;
+    }
+
+    private void reoprtExcel(int examId, String subjectCode) throws IOException, Exception {
+        uploadRange(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "课程分段统计");
+        uploadCollege(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "学院分析");
+        uploadClass(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "班级分析");
+        uploadTeacher(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "任课老师统计");
+        uploadQuestion(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), true, "客观题分析");
+        uploadQuestion(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), false, "主观题分析");
+        uploadGroup(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "分组统计分析");
+    }
+
+    private void uploadGroup(int examId, String subjectCode, String subjectName, String name) throws IOException,
+            Exception {
+        ReportSubjectQuery query = new ReportSubjectQuery();
+        query.setExamId(examId);
+        query.setSubjectCode(subjectCode);
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        List<ReportSubjectGroup> list = reportSubjectGroupService.findByQuery(query);
+        for (ReportSubjectGroup r : list) {
+            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setScoreRate(new BigDecimal(r.getScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setStdev(new BigDecimal(r.getStdev()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setCoefficient(new BigDecimal(r.getCoefficient()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectGroup.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private void uploadQuestion(int examId, String subjectCode, String subjectName, boolean objective, String name)
+            throws IOException, Exception {
+        ReportSubjectQuery query = new ReportSubjectQuery();
+        query.setExamId(examId);
+        query.setSubjectCode(subjectCode);
+        query.setObjective(objective);
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        List<ReportSubjectQuestion> list = reportSubjectQuestionService.findByQuery(query);
+        for (ReportSubjectQuestion r : list) {
+            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setStdev(new BigDecimal(r.getStdev()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setScoreRate(new BigDecimal(r.getScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setFullScoreRate(new BigDecimal(r.getFullScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectQuestion.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private void uploadTeacher(int examId, String subjectCode, String subjectName, String name) throws IOException,
+            Exception {
+        ReportSubjectQuery query = new ReportSubjectQuery();
+        query.setExamId(examId);
+        query.setSubjectCode(subjectCode);
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        List<ReportSubjectTeacher> list = reportSubjectTeacherService.findByQuery(query);
+        for (ReportSubjectTeacher r : list) {
+            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setRelativeAvgScore(new BigDecimal(r.getRelativeAvgScore()).setScale(2, RoundingMode.HALF_UP)
+                    .doubleValue());
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectTeacher.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private void uploadClass(int examId, String subjectCode, String subjectName, String name) throws IOException,
+            Exception {
+        ReportSubjectQuery query = new ReportSubjectQuery();
+        query.setExamId(examId);
+        query.setSubjectCode(subjectCode);
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        List<ReportSubjectClass> list = reportSubjectClassService.findByQuery(query);
+        for (ReportSubjectClass r : list) {
+            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectClass.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private void uploadCollege(int examId, String subjectCode, String subjectName, String name) throws IOException,
+            Exception {
+        ReportSubjectQuery query = new ReportSubjectQuery();
+        query.setExamId(examId);
+        query.setSubjectCode(subjectCode);
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        List<ReportSubjectCollege> list = reportSubjectCollegeService.findByQuery(query);
+        for (ReportSubjectCollege r : list) {
+            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectCollege.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private void uploadRange(int examId, String subjectCode, String subjectName, String name) throws IOException,
+            Exception {
+        List<ReportSubjectRangeDTO> list = new ArrayList<ReportSubjectRangeDTO>();
+        ReportSubject subject = reportSubjectService.findOne(examId, subjectCode);
+        if (subject != null) {
+            JSONArray array = getScoreRange(subject.getScoreRange(), subject.getTotalScore(),
+                    subject.getRealityCount(), 10);
+            for (int i = 0; i < array.size(); i++) {
+                JSONObject jsonObject = array.getJSONObject(i);
+                String score = jsonObject.getInt("score") + "-";
+                Integer rangeCount = jsonObject.getInt("rangeCount");
+                Double rangeRate = jsonObject.getDouble("rangeRate");
+                list.add(new ReportSubjectRangeDTO(score, rangeCount, rangeRate));
+            }
+        }
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        new ExportExcel(name, ReportSubjectRangeDTO.class).setDataList(list).write(os);
+        os.flush();
+        byte[] value = os.toByteArray();
+        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
+        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
+                FormatType.XLSX);
+    }
+
+    private JSONArray getScoreRange(String scoreRange, double totalScore, Integer totalCount, int range) {
+        JSONArray result = new JSONArray();
+        JSONObject jsonObject = JSONObject.fromObject(scoreRange);
+        int rangeCount = 0;
+        int sumCount = 0;
+        int total = (int) Math.ceil(totalScore / range);
+        for (int i = total; i >= 0; i--) {
+            int start = i * range;
+            int end = (i - 1) * range + 1;
+            if (start > totalScore) {
+                start = (int) Math.ceil(totalScore);
+            }
+            if (end < 0) {
+                end = 0;
+            }
+            rangeCount = getSumCount(jsonObject, start, end);
+            sumCount = sumCount + rangeCount;
+            result.add(getRangeJson(totalCount, rangeCount, sumCount, i * range));
+        }
+        return result;
+    }
+
+    private int getSumCount(JSONObject jsonObject, int start, int end) {
+        int sumCount = 0;
+        int currentCount = 0;
+        if (start < end) {
+            for (int i = start; i <= end; i++) {
+                currentCount = jsonObject.getInt(String.valueOf(i));
+                sumCount = sumCount + currentCount;
+            }
+        } else if (start >= end) {
+            for (int i = end; i <= start; i++) {
+                currentCount = jsonObject.getInt(String.valueOf(i));
+                sumCount = sumCount + currentCount;
+            }
+        } else {
+            sumCount = jsonObject.getInt(String.valueOf(start));
+        }
+        return sumCount;
+    }
+
+    private JSONObject getRangeJson(Integer totalCount, int rangeCount, int sumCount, int score) {
+        JSONObject value = new JSONObject();
+        value.accumulate("score", score);
+        value.accumulate("rangeCount", rangeCount);
+        value.accumulate("rangeRate", rangeCount * 100.0 / totalCount);
+        value.accumulate("sumCount", sumCount);
+        value.accumulate("sumRate", sumCount * 100.0 / totalCount);
+        return value;
+    }
 }

+ 1 - 1
stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/ReportSubjectRangeDTO.java → stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/ReportSubjectRangeDTO.java

@@ -1,4 +1,4 @@
-package cn.com.qmth.stmms.admin.dto;
+package cn.com.qmth.stmms.biz.report.utils;
 
 import java.text.DecimalFormat;
 

+ 518 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/utils/ExportExcel.java

@@ -0,0 +1,518 @@
+package cn.com.qmth.stmms.biz.utils;
+
+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.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Comment;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+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 cn.com.qmth.stmms.common.annotation.ExcelField;
+import cn.com.qmth.stmms.common.utils.Encodes;
+import cn.com.qmth.stmms.common.utils.Reflections;
+
+import com.google.common.collect.Lists;
+
+/**
+ * 导出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=" + Encodes.urlEncode(fileName));
+        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.");
+    //
+    // }
+
+}

+ 0 - 126
stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/ScoreReportJob.java

@@ -1,126 +0,0 @@
-package cn.com.qmth.stmms.admin.thread;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
-import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
-import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
-import cn.com.qmth.stmms.biz.exam.service.ExamService;
-import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
-import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
-import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
-import cn.com.qmth.stmms.biz.file.service.FileService;
-import cn.com.qmth.stmms.biz.lock.LockService;
-import cn.com.qmth.stmms.biz.report.service.ReportService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectClassService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectCollegeService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectGroupService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectQuestionService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectService;
-import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherService;
-import cn.com.qmth.stmms.common.enums.LockType;
-import cn.com.qmth.stmms.common.enums.MarkStatus;
-
-/**
- * 统计定时任务
- *
- */
-@Component
-public class ScoreReportJob {
-
-    protected static final Logger log = LoggerFactory.getLogger(ScoreReportJob.class);
-
-    @Autowired
-    private ExamSubjectService subjectService;
-
-    @Autowired
-    private ExamQuestionService questionService;
-
-    @Autowired
-    private MarkGroupService groupService;
-
-    @Autowired
-    private ExamService examService;
-
-    @Autowired
-    private FileService fileService;
-
-    @Autowired
-    private LockService lockService;
-
-    @Autowired
-    private ExamStudentService studentService;
-
-    @Autowired
-    private ReportService reportService;
-
-    @Autowired
-    private ReportSubjectQuestionService reportSubjectQuestionService;
-
-    @Autowired
-    private ReportSubjectClassService reportSubjectClassService;
-
-    @Autowired
-    private ReportSubjectTeacherService reportSubjectTeacherService;
-
-    @Autowired
-    private ReportSubjectCollegeService reportSubjectCollegeService;
-
-    @Autowired
-    private ReportSubjectGroupService reportSubjectGroupService;
-
-    @Autowired
-    private ReportSubjectService reportSubjectService;
-
-    @Qualifier("task-executor")
-    @Autowired
-    private AsyncTaskExecutor taskExecutor;
-
-    /**
-     * 定时更新成绩分析报告
-     */
-    @Scheduled(fixedDelay = 30 * 60 * 1000, initialDelay = 2 * 60 * 1000)
-    public void updateReport() {
-        log.info("start auto-update report");
-        try {
-            Map<Integer, Set<String>> map = new HashMap<Integer, Set<String>>();
-            List<Integer> examIds = groupService.findExamIdByStatus(MarkStatus.FORMAL);
-            for (Integer examId : examIds) {
-                List<ExamSubject> list = subjectService.list(examId);
-                Set<String> set = new HashSet<>();
-                for (ExamSubject subject : list) {
-                    List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(),
-                            subject.getCode(), MarkStatus.TRIAL);
-                    if (!groups.isEmpty()
-                            && !lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
-                        set.add(subject.getCode());
-                    }
-                }
-                if (!set.isEmpty()) {
-                    map.put(examId, set);
-                }
-            }
-            ScoreReportThread thread = new ScoreReportThread(map, lockService, studentService, questionService,
-                    groupService, reportService, examService, subjectService, fileService, false,
-                    reportSubjectQuestionService, reportSubjectClassService, reportSubjectTeacherService,
-                    reportSubjectCollegeService, reportSubjectGroupService, reportSubjectService);
-            taskExecutor.submit(thread);
-        } catch (Exception e) {
-            log.error("auto-update report error", e);
-        } finally {
-            log.info("finish auto-update report");
-        }
-    }
-}

+ 1 - 1
stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/ScoreReportThread.java

@@ -20,7 +20,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
-import cn.com.qmth.stmms.admin.dto.ReportSubjectRangeDTO;
 import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
@@ -49,6 +48,7 @@ import cn.com.qmth.stmms.biz.report.service.ReportSubjectQuestionService;
 import cn.com.qmth.stmms.biz.report.service.ReportSubjectService;
 import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherService;
 import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.ReportSubjectRangeDTO;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkStatus;