From 9073fd9180ded037611106872b0f75b69366c71e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=9F=B3=E5=A4=B4=E4=BA=BA?= <3076767823@qq.com>
Date: Sun, 19 Jan 2025 16:00:40 +0800
Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=A8=A1?=
=?UTF-8?q?=E6=9D=BF=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 33 +
.../com/dpkj/common/config/MvcConfig.java | 37 +
.../controller/TemplateController.java | 48 +
.../service/impl/TemplateService.java | 326 +
src/main/resources/static/js/error.js | 0
.../resources/static/js/getQueryString.js | 28 +
.../static/js/giteeBindOrRegister.js | 88 +
src/main/resources/static/js/jquery351.js | 10872 ++++++++++++++++
src/main/resources/templates/hospital.html | 41 +
9 files changed, 11473 insertions(+)
create mode 100644 src/main/java/com/dpkj/common/config/MvcConfig.java
create mode 100644 src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java
create mode 100644 src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java
create mode 100644 src/main/resources/static/js/error.js
create mode 100644 src/main/resources/static/js/getQueryString.js
create mode 100644 src/main/resources/static/js/giteeBindOrRegister.js
create mode 100644 src/main/resources/static/js/jquery351.js
create mode 100644 src/main/resources/templates/hospital.html
diff --git a/pom.xml b/pom.xml
index 3f0b1c6..1bc4585 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,12 +71,45 @@
${pdfbox.version}
+
com.alibaba
fastjson
${fastjson.version}
+
+
+ org.thymeleaf
+ thymeleaf-spring5
+
+
+
+
+ org.xhtmlrenderer
+ flying-saucer-core
+ 9.1.22
+
+
+
+
+ com.google.zxing
+ core
+ 3.4.1
+
+
+
+
+ org.jsoup
+ jsoup
+ 1.15.4
+
+
+
+ ognl
+ ognl
+ 3.1.28
+
diff --git a/src/main/java/com/dpkj/common/config/MvcConfig.java b/src/main/java/com/dpkj/common/config/MvcConfig.java
new file mode 100644
index 0000000..de2b42e
--- /dev/null
+++ b/src/main/java/com/dpkj/common/config/MvcConfig.java
@@ -0,0 +1,37 @@
+package com.dpkj.common.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+
+/**
+ * mvcConfig配置类
+ *
+ * @author 石头人
+ * @since 2023-05-23 15:24:43
+ */
+@RequiredArgsConstructor
+@Configuration
+public class MvcConfig extends WebMvcConfigurationSupport {
+
+ /**
+ * 添加资源过滤处理器
+ * @param registry 资源处理器
+ */
+ @Override
+ protected void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/**")
+ .addResourceLocations("classpath:/static/");
+ registry.addResourceHandler("/swagger-ui.html/**")
+ .addResourceLocations("classpath:/META-INF/resources/");
+ registry.addResourceHandler("/webjars/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/");
+ registry.addResourceHandler("/swagger-ui/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
+ .resourceChain(false);
+
+ super.addResourceHandlers(registry);
+ }
+
+}
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java
new file mode 100644
index 0000000..07eeeff
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java
@@ -0,0 +1,48 @@
+package com.dpkj.modules.autoReplyPrint.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.dpkj.common.vo.Result;
+import com.dpkj.modules.autoReplyPrint.service.impl.TemplateService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 打印模板控制层
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 15:25:03
+ */
+@RestController
+@RequestMapping("/autoReplyPrint")
+public class TemplateController {
+
+ @Resource
+ private TemplateService templateService;
+
+
+ @GetMapping("/template/{templateName}")
+ public Result testTemplate(@RequestParam Integer width, @RequestParam Integer height, String jsonData, @PathVariable String templateName) {
+ this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), templateName, width, height);
+ return Result.ok("模板生成成功");
+ }
+
+ @GetMapping("/template")
+ public Result testTemplate(String jsonData, @RequestParam Integer width, @RequestParam Integer height) {
+ String html = "\n" +
+ "\n" +
+ "Test\n" +
+ "\n" +
+ "你好,我是一个好的模板
\n" +
+ "
\n" +
+ "\n" +
+ "";
+ this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), html, width, height);
+ return Result.ok("模板生成成功");
+ }
+
+}
+
+
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java
new file mode 100644
index 0000000..783e770
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java
@@ -0,0 +1,326 @@
+package com.dpkj.modules.autoReplyPrint.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.dpkj.common.exception.RRException;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import lombok.extern.slf4j.Slf4j;
+import org.jsoup.Jsoup;
+import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
+import org.springframework.stereotype.Service;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.StringTemplateResolver;
+import org.w3c.dom.Document;
+import org.xhtmlrenderer.layout.SharedContext;
+import org.xhtmlrenderer.simple.Graphics2DRenderer;
+
+import javax.imageio.ImageIO;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.thymeleaf.context.Context;
+
+/**
+ * 模板服务类
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-18 15:33:51
+ */
+@Service
+@Slf4j
+public class TemplateService {
+
+
+ /**
+ * 生成小票图片
+ * @param data json数据,用来填充模板
+ * @param template 模板(html字符串或者模板名称)
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @return 图片字节数组
+ */
+ public byte[] generateReceiptImage(JSONObject data, String template, int width, int height) {
+ try {
+ // 获取模板上下文
+ Context context = this.getContext(data);
+
+ TemplateEngine templateEngine = new TemplateEngine();
+ if (checkIsHtml(template)) {
+ StringTemplateResolver resolver = new StringTemplateResolver();
+ resolver.setTemplateMode("HTML");
+ // 设置模板引擎使用这个自定义的模板解析器
+ templateEngine.setTemplateResolver(resolver);
+ }else {
+ SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
+ resolver.setPrefix("classpath:/templates/");
+ resolver.setSuffix(".html");
+ resolver.setTemplateMode(TemplateMode.HTML);
+ resolver.setCacheable(true);
+ resolver.setApplicationContext(new AnnotationConfigReactiveWebApplicationContext());
+ templateEngine.setTemplateResolver(resolver);
+ }
+
+ // 渲染模板
+ String html = templateEngine.process(template, context);
+
+ return this.generate(html, width, height);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RRException("图片打印失败");
+ }
+ }
+
+
+ /**
+ * 生成图片
+ * @param html html内容
+ */
+ private byte[] generate(String html, int width, int height){
+ try {
+ // 转换为xhtml
+ String xhtml = this.htmlToXhtml(html);
+
+ // 转换为document
+ Document document = this.xhtmlToDocument(xhtml);
+
+ // 生成图片
+ BufferedImage image = createImageToDocument(document, width, height);
+
+ // 保存图片
+ String outputPath = "E:\\images\\test_" + System.currentTimeMillis() + ".png";
+ ImageIO.write(image, "PNG", new File(outputPath));
+
+ ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+ ImageIO.write(image, "PNG", byteOutputStream);
+ return byteOutputStream.toByteArray();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RRException("图片打印失败");
+ }
+ }
+
+
+ /**
+ * 通过JsonObject进行context内容填充
+ * @param data jsonObject
+ * @return context
+ */
+ private Context getContext(JSONObject data) {
+ // 创建Thymeleaf上下文
+ Context context = new Context();
+ Set keys = data.keySet();
+ for (String key : keys) {
+ // 判单是否有图片生成,统一后面采用的是_2base64Type
+ String[] split = key.split("_");
+ if (split.length > 1 && split[1].equals("2base64Type")) {
+ int width = split.length > 2 ? Integer.parseInt(split[2]) : 100;
+ int height = split.length > 3 ? Integer.parseInt(split[3]) : 100;
+ // 如果是图片类型,需要进行base64转换
+ String base64 = this.generateQRCode(String.valueOf(data.get(key)), width, height);
+ context.setVariable(split[0], "data:image/jpeg;base64," + base64);
+ } else {
+ // 普通字段直接设置
+ context.setVariable(key, data.get(key));
+ }
+ }
+
+ return context;
+ }
+
+
+ /**
+ * 根据内容生成二维码
+ * @param content 转换内容
+ */
+ private String generateQRCode(String content, int width, int height) {
+ try {
+ Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码为 UTF-8
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 设置纠错级别
+
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
+
+ BufferedImage qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ qrImage.createGraphics();
+
+ Graphics2D graphics = (Graphics2D) qrImage.getGraphics();
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, width, height);
+ graphics.setColor(Color.BLACK);
+
+ for (int i = 0; i < width; i++) {
+ for (int j = 0; j < height; j++) {
+ if (bitMatrix.get(i, j)) {
+ graphics.fillRect(i, j, 1, 1);
+ }
+ }
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(qrImage, "png", baos);
+ return Base64.getEncoder().encodeToString(baos.toByteArray());
+ }catch (Exception e){
+ log.error("二维码生成失败");
+ throw new RRException("二维码生成失败");
+ }
+ }
+
+
+ /**
+ * xhtml 转换为 Document
+ * @param xhtml xhtml
+ * @return document
+ * @throws Exception e
+ */
+ private Document xhtmlToDocument(String xhtml) throws Exception {
+ // 创建DocumentBuilder
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // 解析XHTML字符串为Document
+ return builder.parse(new ByteArrayInputStream(xhtml.getBytes(StandardCharsets.UTF_8)));
+ }
+
+
+ /**
+ * 转换,将html转换为xhtml
+ * @param html html内容
+ * @return xhtml
+ */
+ private String htmlToXhtml(String html) {
+ org.jsoup.nodes.Document doc = Jsoup.parse(html);
+ doc.outputSettings()
+ .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
+ .charset(StandardCharsets.UTF_8);
+
+ // 不使用外部DTD
+ return "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ doc.body().html() +
+ "\n\n" +
+ "";
+ }
+
+
+ /**
+ * 通过document转换为图片
+ * @param document doc
+ * @param width 图片的宽度
+ * @param height 图片的高度
+ * @return bufferedImage
+ */
+ private BufferedImage createImageToDocument(Document document, int width, int height) {
+ try {
+ // 创建Dimension对象
+ Dimension dimension = new Dimension(width, height);
+
+ // 创建图片
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+
+ // 设置白色背景
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, width, height);
+
+ // 设置渲染提示
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ // 创建渲染器
+ Graphics2DRenderer renderer = new Graphics2DRenderer();
+ renderer.setDocument(document, null);
+
+ // 设置渲染参数
+ SharedContext sharedContext = renderer.getSharedContext();
+ sharedContext.setInteractive(false);
+ sharedContext.setDPI(256f);
+
+ // 使用Dimension对象进行布局
+ renderer.layout(graphics, dimension);
+ renderer.render(graphics);
+
+ graphics.dispose();
+ return image;
+ } catch (Exception e) {
+ throw new RRException("渲染图片失败", e);
+ }
+ }
+
+
+ /**
+ * 校验这个模板内容是不是html字符串,而非模板名称
+ * @param template template
+ * @return 是否是html字符串
+ */
+ private boolean checkIsHtml(String template){
+ try {
+ String pattern = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>";
+ Pattern r = Pattern.compile(pattern);
+ return r.matcher(template).find();
+ }catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * 将document对象转换为字符串
+ * @param doc document
+ * @return document转换为的字符串
+ */
+ private String documentToString(Document doc) {
+ try {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+ return writer.getBuffer().toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
+
diff --git a/src/main/resources/static/js/error.js b/src/main/resources/static/js/error.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/static/js/getQueryString.js b/src/main/resources/static/js/getQueryString.js
new file mode 100644
index 0000000..e380d57
--- /dev/null
+++ b/src/main/resources/static/js/getQueryString.js
@@ -0,0 +1,28 @@
+/**
+ * 获取路径参数
+ * @returns {{}}
+ */
+function getQueryString() {
+ let qs = location.search.substring(1), // 获取url中"?"符后的字串
+ args = {}, // 保存参数数据的对象
+ items = qs.length ? qs.split("&") : [], // 取得每一个参数项,
+ item = null,
+ len = items.length;
+
+ for(let i = 0; i < len; i++) {
+ item = items[i].split("=");
+ let name = decodeURIComponent(item[0]),
+ value = decodeURIComponent(item[1]);
+ if(name) {
+ args[name] = value;
+ }
+ }
+ return args;
+}
+
+/**
+ * 前往首页
+ */
+function loginToIndexFn(loginToIndex=getQueryString()["loginToIndex"]){
+ window.location.assign(loginToIndex);
+}
\ No newline at end of file
diff --git a/src/main/resources/static/js/giteeBindOrRegister.js b/src/main/resources/static/js/giteeBindOrRegister.js
new file mode 100644
index 0000000..ea18bdd
--- /dev/null
+++ b/src/main/resources/static/js/giteeBindOrRegister.js
@@ -0,0 +1,88 @@
+/**
+ * 获取路径参数
+ */
+let myLoginToIndex = "";
+let registerBind = "";
+let bindExisting = "";
+try {
+ let queryString = getQueryString();
+ myLoginToIndex = queryString["loginToIndex"]
+ registerBind = queryString["registerBind"]
+ bindExisting = queryString['bindExisting']
+} catch (ignore){}
+
+/**
+ * 注册并绑定
+ */
+function registerAndBind(){
+ let email = $("#email").val();
+ $.ajax({
+ url: registerBind,
+ type: 'POST',
+ data: {
+ email: email
+ },
+ success: function (result){
+ if ( result.code === 200 ){
+ window.location.assign(myLoginToIndex);
+ }else {
+ alert(result.message);
+ }
+ }
+ })
+}
+
+/**
+ * 点击展示或者隐藏绑定输入框
+ */
+function show(){
+ let bindChange = document.getElementById("bind");
+ let computedStyle = window.getComputedStyle(bindChange);
+ let displayValue = computedStyle.getPropertyValue("display");
+ let beginValue = displayValue;
+ displayValue = displayValue === "none" ? "block" : "none";
+ $("#bind").css("display", displayValue);
+ $("#register-email").css("display", "none");
+}
+
+/**
+ * 点击展示输入注册邮箱的输入框
+ */
+function registerAndBindShow() {
+ let bindChange = document.getElementById("register-email");
+ let computedStyle = window.getComputedStyle(bindChange);
+ let displayValue = computedStyle.getPropertyValue("display");
+ let beginValue = displayValue;
+ displayValue = displayValue === "none" ? "block" : "none";
+ // computedStyle.setProperty("display", displayValue)
+ $("#bind").css("display", "none");
+ $("#register-email").css("display", displayValue);
+
+}
+
+/**
+ * 绑定已经注册了的账号
+ */
+function bindExistingAccount(){
+ let username = document.getElementById("username").value;
+ let password = document.getElementById("password").value;
+ if ( username === "" || password === ""){
+ alert("用户名或者密码未进行输入");
+ return;
+ }
+ $.ajax({
+ url: bindExisting,
+ type: 'POST',
+ data: {
+ account: username,
+ password: password
+ },
+ success: function (result){
+ if ( result.code === 200 ){
+ window.location.assign(myLoginToIndex);
+ }else {
+ alert(result.message);
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/src/main/resources/static/js/jquery351.js b/src/main/resources/static/js/jquery351.js
new file mode 100644
index 0000000..4adbfa5
--- /dev/null
+++ b/src/main/resources/static/js/jquery351.js
@@ -0,0 +1,10872 @@
+/*!
+ * jQuery JavaScript Library v3.5.1
+ * https://jquery.com/
+ *
+ * Includes Sizzle.js
+ * https://sizzlejs.com/
+ *
+ * Copyright JS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2020-05-04T22:49Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML