diff --git a/src/main/java/cn/teammodel/TeamModelExtensionApplication.java b/src/main/java/cn/teammodel/TeamModelExtensionApplication.java index b25bb74..2ee5074 100644 --- a/src/main/java/cn/teammodel/TeamModelExtensionApplication.java +++ b/src/main/java/cn/teammodel/TeamModelExtensionApplication.java @@ -10,4 +10,6 @@ public class TeamModelExtensionApplication { SpringApplication.run(TeamModelExtensionApplication.class, args); } + + } diff --git a/src/main/java/cn/teammodel/controller/frontend/HelloController.java b/src/main/java/cn/teammodel/controller/frontend/HelloController.java index 126e3ec..a237804 100644 --- a/src/main/java/cn/teammodel/controller/frontend/HelloController.java +++ b/src/main/java/cn/teammodel/controller/frontend/HelloController.java @@ -1,12 +1,18 @@ package cn.teammodel.controller.frontend; -import cn.hutool.core.map.MapUtil; import cn.teammodel.common.R; import cn.teammodel.repository.AppraiseRepository; import cn.teammodel.service.EvaluationService; +import cn.teammodel.utils.ChartUtil; import cn.teammodel.utils.PdfUtil; import com.itextpdf.text.DocumentException; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfStamper; import io.swagger.annotations.Api; +import org.jfree.chart.ChartUtils; +import org.jfree.chart.JFreeChart; +import org.jfree.data.general.DefaultPieDataset; +import org.springframework.core.io.ClassPathResource; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; @@ -14,8 +20,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Map; @RestController @@ -43,8 +52,36 @@ public class HelloController { @GetMapping("public/pdf") public void freepdf(HttpServletResponse response) throws DocumentException, IOException { - Map data = MapUtil.ofEntries(MapUtil.entry("Text1", "1000"), MapUtil.entry("Text2", "2000"), MapUtil.entry("Text3", "3000")); - PdfUtil.fillStudentPdfForm(data, response); + // 设置response参数 + response.reset(); + response.setContentType("application/pdf"); + response.setHeader("Content-disposition", + "attachment;filename=report_student_" + System.currentTimeMillis() + ".pdf"); + ClassPathResource resource = new ClassPathResource("templates/pdf_templates/student_report.pdf"); + InputStream in = resource.getInputStream(); + ServletOutputStream os = response.getOutputStream(); + // 处理 stampter + PdfReader pdfReader = new PdfReader(in); + PdfStamper stamper = new PdfStamper(pdfReader, os); + + Map data = PdfUtil.data(); + DefaultPieDataset dataset = new DefaultPieDataset( ); + dataset.setValue( "IPhone 5s" , new Double( 20 ) ); + dataset.setValue( "SamSung Grand" , new Double( 20 ) ); + dataset.setValue( "MotoG" , new Double( 40 ) ); + dataset.setValue( "Nokia Lumia" , new Double( 10 ) ); + + JFreeChart pieChart = ChartUtil.pieChart("手机销量统计", dataset); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ChartUtils.writeChartAsJPEG(bos, pieChart, 850, 440); + // 填充表单 + PdfUtil.fillPdfForm(stamper, data); + PdfUtil.fillImage(stamper, "praiseDistribution", bos.toByteArray()); + PdfUtil.fillImage(stamper, "criticalDistribution", bos.toByteArray()); + // 关闭流 + stamper.setFormFlattening(true); + stamper.close(); + os.close(); } diff --git a/src/main/java/cn/teammodel/model/vo/appraise/StudentReportVo.java b/src/main/java/cn/teammodel/model/vo/appraise/StudentReportVo.java index 24a3df8..b39dc18 100644 --- a/src/main/java/cn/teammodel/model/vo/appraise/StudentReportVo.java +++ b/src/main/java/cn/teammodel/model/vo/appraise/StudentReportVo.java @@ -20,6 +20,9 @@ import java.util.Map; @Data @Builder public class StudentReportVo { + @ApiModelProperty("学生名字") + private String name; + @ApiModelProperty("学生表扬总数") private Integer praiseCount; diff --git a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java index 964069c..5824ff9 100644 --- a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java +++ b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java @@ -18,13 +18,21 @@ import cn.teammodel.model.vo.appraise.StudentReportVo; import cn.teammodel.repository.*; import cn.teammodel.security.utils.SecurityUtil; import cn.teammodel.service.EvaluationService; +import cn.teammodel.utils.ChartUtil; +import cn.teammodel.utils.PdfUtil; import cn.teammodel.utils.RepositoryUtil; import cn.teammodel.utils.SchoolDateUtil; import com.azure.cosmos.models.CosmosPatchOperations; import com.azure.spring.data.cosmos.core.query.CosmosPageRequest; import com.itextpdf.text.DocumentException; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfStamper; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.jfree.chart.ChartUtils; +import org.jfree.chart.JFreeChart; +import org.jfree.data.general.DefaultPieDataset; +import org.springframework.core.io.ClassPathResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -32,7 +40,9 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.time.Instant; import java.time.LocalDate; import java.util.*; @@ -591,6 +601,7 @@ public class EvaluationServiceImpl implements EvaluationService { String topCriticalNode = criticalNodeMap.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null); return StudentReportVo.builder() + .name(student.getName()) .praiseCount(praiseCount) .score(appraiseRecord.getScore()) .beyondPercent(beyondPercent) @@ -608,10 +619,59 @@ public class EvaluationServiceImpl implements EvaluationService { @Override public void exportStuReportPdf(IdRequest idRequest, HttpServletResponse response) throws IOException, DocumentException{ - - + // 设置response参数 + response.reset(); + response.setContentType("application/pdf"); + response.setHeader("Content-disposition", + "attachment;filename=report_student_" + System.currentTimeMillis() + ".pdf"); + ClassPathResource resource = new ClassPathResource("templates/pdf_templates/student_report.pdf"); + InputStream in = resource.getInputStream(); ServletOutputStream os = response.getOutputStream(); + // 获取报告数据 + StudentReportVo reportVo = this.studentReport(idRequest); + // 处理 stampter + PdfReader pdfReader = new PdfReader(in); + PdfStamper stamper = new PdfStamper(pdfReader, os); + + Map data = new HashMap<>(); + data.put("name", reportVo.getName()); + data.put("praiseCount", String.valueOf(reportVo.getPraiseCount())); + data.put("beyondPercent", String.valueOf(reportVo.getBeyondPercent())); + data.put("topPraiseTeacher", reportVo.getTopPraiseTeacher().getName()); + data.put("topCriticalTeacher", reportVo.getTopCriticalTeacher().getName()); + data.put("topPraiseNode", reportVo.getTopPraiseNode()); + data.put("topCriticalNode", reportVo.getTopCriticalNode()); + + DefaultPieDataset praiseDistributionDataset = new DefaultPieDataset( ); + praiseDistributionDataset.setValue(FiveEducations.VIRTUE.getName(), reportVo.getPraiseDistribution().get(FiveEducations.VIRTUE.getCode())); + praiseDistributionDataset.setValue(FiveEducations.INTELLIGENCE.getName(), reportVo.getPraiseDistribution().get(FiveEducations.INTELLIGENCE.getCode())); + praiseDistributionDataset.setValue(FiveEducations.SPORTS.getName(), reportVo.getPraiseDistribution().get(FiveEducations.SPORTS.getCode())); + praiseDistributionDataset.setValue(FiveEducations.ART.getName(), reportVo.getPraiseDistribution().get(FiveEducations.ART.getCode())); + praiseDistributionDataset.setValue(FiveEducations.LABOUR.getName(), reportVo.getPraiseDistribution().get(FiveEducations.LABOUR.getCode())); + JFreeChart praisePieChart = ChartUtil.pieChart("五育表扬指标分布", praiseDistributionDataset); + ByteArrayOutputStream praiseBos = new ByteArrayOutputStream(); + ChartUtils.writeChartAsJPEG(praiseBos, praisePieChart, 340, 330); + + DefaultPieDataset criticalDistributionDataset = new DefaultPieDataset( ); + criticalDistributionDataset.setValue(FiveEducations.VIRTUE.getName(), reportVo.getCriticalDistribution().get(FiveEducations.VIRTUE.getCode())); + criticalDistributionDataset.setValue(FiveEducations.INTELLIGENCE.getName(), reportVo.getCriticalDistribution().get(FiveEducations.INTELLIGENCE.getCode())); + criticalDistributionDataset.setValue(FiveEducations.SPORTS.getName(), reportVo.getCriticalDistribution().get(FiveEducations.SPORTS.getCode())); + criticalDistributionDataset.setValue(FiveEducations.ART.getName(), reportVo.getCriticalDistribution().get(FiveEducations.ART.getCode())); + criticalDistributionDataset.setValue(FiveEducations.LABOUR.getName(), reportVo.getCriticalDistribution().get(FiveEducations.LABOUR.getCode())); + JFreeChart criticalPieChart = ChartUtil.pieChart("五育待改进指标分布", criticalDistributionDataset); + ByteArrayOutputStream criticalBos = new ByteArrayOutputStream(); + ChartUtils.writeChartAsJPEG(criticalBos, criticalPieChart, 340, 330); + + // 填充表单 + PdfUtil.fillPdfForm(stamper, data); + PdfUtil.fillImage(stamper, "praiseDistribution", praiseBos.toByteArray()); + PdfUtil.fillImage(stamper, "criticalDistribution", criticalBos.toByteArray()); + // 关闭流 + stamper.setFormFlattening(true); + stamper.close(); + os.close(); + } /** diff --git a/src/main/java/cn/teammodel/utils/ChartUtil.java b/src/main/java/cn/teammodel/utils/ChartUtil.java new file mode 100644 index 0000000..fc7796a --- /dev/null +++ b/src/main/java/cn/teammodel/utils/ChartUtil.java @@ -0,0 +1,181 @@ +package cn.teammodel.utils; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.StandardChartTheme; +import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; +import org.jfree.chart.labels.StandardPieSectionLabelGenerator; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.PiePlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.chart.renderer.category.LineAndShapeRenderer; +import org.jfree.chart.renderer.category.StandardBarPainter; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.general.DefaultPieDataset; + +import java.awt.*; +import java.text.DecimalFormat; +import java.text.NumberFormat; + +/** + * 生成图表工具类 + * @author winter + * @create 2023-10-30 11:12 + */ +public class ChartUtil { + private static final Color[] BAR_COLORS = new Color[]{ + new Color(79,129,189), + new Color(192, 80, 77), + new Color(155, 187, 89), + }; + + private static final Color[] LINE_COLORS = new Color[]{ + new Color(237, 123, 46), + new Color(90, 154, 213), + new Color(164, 164, 164), + }; + + private static final Color[] PIE_COLORS = new Color[]{ + new Color(75, 172, 198), + new Color(128, 100, 162), + new Color(155, 187, 89), + new Color(192, 80, 77), + new Color(79, 129, 189), + new Color(44, 77, 117), + new Color(247, 150, 70), + new Color(165, 165, 165), + }; + + + private static StandardChartTheme initChartTheme(){ + StandardChartTheme currentTheme = new StandardChartTheme("JFree"); + // 横轴纵轴标题文字大小 + currentTheme.setLargeFont(new Font("宋体", Font.BOLD, 15)); + // 横轴纵轴数值文字大小 + currentTheme.setRegularFont(new Font("宋体", Font.PLAIN, 13)); + currentTheme.setExtraLargeFont(new Font("宋体", Font.BOLD, 20)); + // 背景颜色 + currentTheme.setPlotBackgroundPaint(new Color(255, 255, 204, 0)); + // 边框线条 + currentTheme.setPlotOutlinePaint(new Color(0, 0, 0, 0)); + // 网格线条 + currentTheme.setRangeGridlinePaint(new Color(78, 74, 74)); + return currentTheme; + } + + /** + * 线图 + * + * @param title 标题 + * @param categoryAxisLabel 分类标签 + * @param valueAxisLabel 数值标签 + * @param dataset 数据集 + * @return org.jfree.chart.JFreeChart + */ + public static JFreeChart lineChart(String title, String categoryAxisLabel, String valueAxisLabel, DefaultCategoryDataset dataset){ + ChartFactory.setChartTheme(initChartTheme()); + + JFreeChart chart = ChartFactory.createLineChart( + title, + categoryAxisLabel, + valueAxisLabel, + dataset, + PlotOrientation.VERTICAL, + true, + true, + false + ); + + CategoryPlot plot = chart.getCategoryPlot(); + LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); + // 折现点显示数值 + renderer.setDefaultItemLabelsVisible(true); + renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator()); + // 更改线条颜色 + for (int i = 0; i < dataset.getRowKeys().size(); i++) { + renderer.setSeriesPaint(i, LINE_COLORS[i]); + } + return chart; + } + + /** + * 柱状图 + * + * @param title + * @param categoryAxisLabel + * @param valueAxisLabel + * @param dataset 数据集 + * @return org.jfree.chart.JFreeChart + */ + public static JFreeChart barChart(String title, String categoryAxisLabel, String valueAxisLabel, DefaultCategoryDataset dataset){ + ChartFactory.setChartTheme(initChartTheme()); + + JFreeChart chart = ChartFactory.createBarChart( + title, + categoryAxisLabel, + valueAxisLabel, + dataset, + PlotOrientation.VERTICAL, + true, + true, + false + ); + + CategoryPlot plot = chart.getCategoryPlot(); + BarRenderer renderer = (BarRenderer) plot.getRenderer(); + // 纯色显示 + renderer.setBarPainter(new StandardBarPainter()); + // 柱子上显示小数字 + renderer.setDefaultItemLabelsVisible(true); + renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator()); + // 设置柱子间隔 + renderer.setItemMargin(0.0); + // 设置柱子颜色 + for (int i = 0; i < dataset.getRowKeys().size(); i++) { + renderer.setSeriesPaint(i, BAR_COLORS[i]); + } + return chart; + } + + /** + * 饼图 + * @param title + * @param dataset + * @return org.jfree.chart.JFreeChart + */ + public static JFreeChart pieChart(String title, DefaultPieDataset dataset){ + ChartFactory.setChartTheme(initChartTheme()); + + JFreeChart chart = ChartFactory.createPieChart( + title, + dataset, + true, + true, + false + ); + PiePlot plot = (PiePlot) chart.getPlot(); + // 设置扇区颜色 + for (int i = 0; i < dataset.getKeys().size(); i++) { + plot.setSectionPaint(dataset.getKey(i), PIE_COLORS[i]); + } + // 设置扇区的线条颜色 + plot.setDefaultSectionOutlinePaint(new Color(255, 255, 255)); + // 设置扇区的线条大小 + plot.setDefaultSectionOutlineStroke(new BasicStroke(3)); + // 设置标签颜色 + plot.setLabelLinkPaint(new Color(255,255,255, 0)); + // 设置标签背景色 + plot.setLabelBackgroundPaint(new Color(255, 255, 255,0)); + // 设置标签线条颜色 + plot.setLabelOutlinePaint(new Color(255, 255, 255, 0)); + // 设置标签阴影颜色 + plot.setLabelShadowPaint(new Color(255, 255, 255, 0)); + // 设置饼图阴影颜色 + plot.setShadowPaint(new Color(255, 255, 255, 0)); + // 添加标签数字百分比显示 + plot.setLabelGenerator(new StandardPieSectionLabelGenerator(("{0}{2}"), NumberFormat.getNumberInstance(),new DecimalFormat("0.00%"))); + return chart; + } +} + diff --git a/src/main/java/cn/teammodel/utils/PdfUtil.java b/src/main/java/cn/teammodel/utils/PdfUtil.java index 5f67ba3..6a95419 100644 --- a/src/main/java/cn/teammodel/utils/PdfUtil.java +++ b/src/main/java/cn/teammodel/utils/PdfUtil.java @@ -1,16 +1,13 @@ package cn.teammodel.utils; import com.itextpdf.text.DocumentException; +import com.itextpdf.text.Image; +import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.AcroFields; -import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfStamper; -import org.springframework.core.io.ClassPathResource; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.Map; /** @@ -18,27 +15,24 @@ import java.util.Map; * @create 2024-01-24 16:02 */ public class PdfUtil { - - public static void fillStudentPdfForm(Map data, HttpServletResponse response) throws IOException, DocumentException { - response.reset(); - response.setContentType("application/pdf"); - response.setHeader("Content-disposition", - "attachment;filename=report_student_" + System.currentTimeMillis() + ".pdf"); - - ClassPathResource resource = new ClassPathResource("templates/pdf_templates/template.pdf"); - InputStream in = resource.getInputStream(); - ServletOutputStream os = response.getOutputStream(); - fillPdfForm(in, os, data); - } - public static void fillPdfForm(InputStream in, OutputStream os, Map data) throws IOException, DocumentException { - PdfReader pdfReader = new PdfReader(in); - PdfStamper stamper = new PdfStamper(pdfReader, os); + public static void fillPdfForm(PdfStamper stamper, Map data) throws IOException, DocumentException { AcroFields fields = stamper.getAcroFields(); for (Map.Entry entry : data.entrySet()) { fields.setField(entry.getKey(), entry.getValue()); } - stamper.setFormFlattening(true); - stamper.close(); - os.close(); } + + public static void fillImage(PdfStamper stamper, String imgFieldName, byte[] data) throws DocumentException, IOException { + AcroFields acroFields = stamper.getAcroFields(); + Image image = Image.getInstance(data); + int pageNo = acroFields.getFieldPositions(imgFieldName).get(0).page; + Rectangle rectangle = acroFields.getFieldPositions(imgFieldName).get(0).position; + float x = rectangle.getLeft(); + float y = rectangle.getBottom(); + PdfContentByte content = stamper.getOverContent(pageNo); + image.scaleToFit(rectangle.getWidth(), rectangle.getHeight()); + image.setAbsolutePosition(x, y); + content.addImage(image); + } + } \ No newline at end of file diff --git a/src/main/resources/templates/pdf_templates/student_report.pdf b/src/main/resources/templates/pdf_templates/student_report.pdf new file mode 100644 index 0000000..6191a2b Binary files /dev/null and b/src/main/resources/templates/pdf_templates/student_report.pdf differ