在什么网站可以接设计做免费网站制作模板
2026/1/5 21:50:32 网站建设 项目流程
在什么网站可以接设计做,免费网站制作模板,黄冈地区免费网站推广平台,常州网站建设设计基于Freemarker与JBIG压缩生成PDF电子凭证 在农村普惠金融业务中#xff0c;POS终端每完成一笔交易#xff0c;用户都需要一张清晰、合规且具备法律效力的电子凭证。这张看似简单的“小票”#xff0c;背后却承载着对账依据、争议处理和监管审计等多重职责。然而#xff0c…基于Freemarker与JBIG压缩生成PDF电子凭证在农村普惠金融业务中POS终端每完成一笔交易用户都需要一张清晰、合规且具备法律效力的电子凭证。这张看似简单的“小票”背后却承载着对账依据、争议处理和监管审计等多重职责。然而在实际落地过程中我们常常面临几个棘手问题原始手写签名图片动辄数百KB大量存储成本高中文字体渲染易乱码系统需支持多类缴费模板并快速响应并发请求。如何在资源受限的环境下高效生成美观、安全、可追溯的PDF凭证本文将分享一套已在生产环境稳定运行的技术方案——通过 Freemarker 模板引擎 JBIG 图像压缩技术实现轻量级、高性能的电子凭证服务。这套系统广泛应用于助农取款、水电煤缴费、广电签约等场景支撑日均数万笔交易。其核心思路是前端采集数据时对签名图进行高压缩处理后端使用模板动态填充内容并将HTML精准转为PDF输出。整个流程兼顾了性能、兼容性与扩展性。技术选型与整体架构我们采用的是SpringMVC Freemarker Flying Saucer (基于 iText) JBIGKit的组合各组件分工明确Freemarker负责模板解析与数据绑定支持灵活的条件判断和格式化逻辑Flying Saucer将结构化的 HTML 渲染成 PDF保留 CSS 样式布局能力iText itext-asian解决中文字符嵌入问题确保字体跨平台一致显示JBIGKit实现二值图像如签名的无损高压缩显著降低传输与存储开销。典型调用链如下POS设备采集用户信息及BMP格式手写签名使用 JBIG 算法将签名压缩为 Hex 字符串压缩比可达 100:1数据以 JSON 形式上传至后端服务后端根据交易类型选择对应 HTML 模板注入交易字段解压签名图像并嵌入 Base64 编码至 HTML利用 Flying Saucer 渲染为 PDF返回预览或下载流。这一设计不仅减少了网络传输压力也避免了服务器端长期保存大体积临时文件的风险。关键依赖配置以下是pom.xml中的核心依赖项dependencies !-- Freemarker 模板引擎 -- dependency groupIdorg.freemarker/groupId artifactIdfreemarker/artifactId version2.3.31/version /dependency !-- HTML to PDF 渲染组件 -- dependency groupIdorg.xhtmlrenderer/groupId artifactIdflying-saucer-pdf-itext5/artifactId version9.1.22/version /dependency dependency groupIdorg.xhtmlrenderer/groupId artifactIdflying-saucer-core/artifactId version9.1.22/version /dependency !-- iText 中文支持 字体嵌入 -- dependency groupIdcom.itextpdf/groupId artifactIditext-asian/artifactId version5.2.0/version /dependency !-- JBIG 图像压缩解压库 -- dependency groupIduk.ac.cam.cl.mgk25.jbigkit/groupId artifactIdjbigkit/artifactId version2.1/version /dependency !-- 工具类 -- dependency groupIdcommons-lang/groupId artifactIdcommons-lang/artifactId version2.6/version /dependency dependency groupIdcom.google.zxing/groupId artifactIdcore/artifactId version3.4.1/version /dependency /dependencies特别说明flying-saucer-pdf-itext5对接的是 iText 5.x 版本若项目已升级到 iText 7请注意 API 不兼容问题。此外itext-asian提供了 SimSun、SimHei 等常用中文字体支持只需在代码中注册即可直接使用。配置常量定义为了统一管理路径、文件名和映射关系我们封装了一个Constants类package com.elec.pdf.util; import java.util.HashMap; import java.util.Map; public class Constants { public interface PDF_CONFIG { String PDF_SIGN_CFG_PATH config/netsignagent.properties; String PDF_FILE_PATH /data/pdf/temp/; String PDF_FONT_PATH templates/fonts/simhei.ttf; String PDF_HEADER_LOGO templates/images/header_logo.jpg; String DOWNLOAD_URL_PREFIX https://fileserver.com/download/; String PDF_SUFFIX .pdf; String JBIG_SUFFIX .jbig; String BMP_SUFFIX .bmp; String JPG_SUFFIX .jpg; String OP_PREVIEW 01; String OP_DOWNLOAD 02; String IMG_BASE64_HEADER data:image/jpeg;base64,; } // 交易码 → 模板文件名 映射 public static final MapString, String TXN_TEMPLATE_MAP new HashMap(); static { TXN_TEMPLATE_MAP.put(200401, ZNWDMTemplate.html); // 助农取款 TXN_TEMPLATE_MAP.put(200402, ZNTRANTemplate.html); // 助农转账 TXN_TEMPLATE_MAP.put(200702, ZNDFJFTemplate.html); // 电费缴费 TXN_TEMPLATE_MAP.put(200907, ZNGDJFTemplate.html); // 广电缴费 TXN_TEMPLATE_MAP.put(201503, ZNSFJFTemplate.html); // 水费缴费 TXN_TEMPLATE_MAP.put(2006042, XDTQHBTemplate.html); // 提前还本 } }这种集中式映射方式极大提升了维护效率。当新增一种交易类型时只需添加一条映射记录无需修改主流程代码。核心工具类实现ElecBillCreateUtils.java—— 主要功能集合该工具类集成了模板渲染、图像转换、编码处理等关键操作。package com.elec.pdf.util; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.itextpdf.text.pdf.BaseFont; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import uk.ac.cam.cl.mgk25.jbigkit.JBIG; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; import java.util.Hashtable; import java.util.Map; public class ElecBillCreateUtils { private static final Logger log LoggerFactory.getLogger(ElecBillCreateUtils.class); private static Configuration fmConfig; static { fmConfig new Configuration(Configuration.VERSION_2_3_31); try { fmConfig.setClassForTemplateLoading(ElecBillCreateUtils.class, /templates); fmConfig.setDefaultEncoding(UTF-8); fmConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); } catch (Exception e) { log.error(初始化 Freemarker 配置失败, e); } } /** * 渲染 Freemarker 模板为 HTML 字符串 */ public static String renderHtml(MapString, Object data, String templateName) throws Exception { Writer out new StringWriter(); Template template fmConfig.getTemplate(templateName); template.process(data, out); return out.toString(); } /** * 将 HTML 内容转为 PDF 字节流 */ public static byte[] htmlToPdf(String htmlContent, String fontPath) throws Exception { ByteArrayOutputStream os new ByteArrayOutputStream(); ITextRenderer renderer new ITextRenderer(); ITextFontResolver fontResolver renderer.getFontResolver(); // 注册中文字体 fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.setDocumentFromString(htmlContent); renderer.layout(); renderer.createPDF(os); renderer.finishPDF(); return os.toByteArray(); } /** * JBIG 压缩数据还原为 BMP 文件 */ public static void jbigToBmp(byte[] jbigData, String outputPath) throws IOException { File tmpFile new File(outputPath Constants.PDF_CONFIG.JBIG_SUFFIX); try (FileOutputStream fos new FileOutputStream(tmpFile)) { fos.write(jbigData); } try { JBIG.jbg2bmp(tmpFile.getAbsolutePath(), outputPath Constants.PDF_CONFIG.BMP_SUFFIX); } catch (Exception e) { throw new IOException(JBIG 解码失败, e); } finally { if (tmpFile.exists()) tmpFile.delete(); } } /** * BMP 转 JPG减小体积便于后续展示 */ public static void bmpToJpg(String bmpPath, String jpgPath) throws IOException { BufferedImage image ImageIO.read(new File(bmpPath)); BufferedImage converted new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); converted.getGraphics().drawImage(image, 0, 0, null); ImageIO.write(converted, jpg, new File(jpgPath)); new File(bmpPath).delete(); // 删除中间文件 } /** * 十六进制字符串转字节数组 */ public static byte[] hexToBytes(String hexStr) { int len hexStr.length(); byte[] bytes new byte[len / 2]; for (int i 0; i len; i 2) { bytes[i / 2] (byte) ((Character.digit(hexStr.charAt(i), 16) 4) Character.digit(hexStr.charAt(i 1), 16)); } return bytes; } /** * 生成二维码并返回 Base64 编码 */ public static String generateQrCodeBase64(String content) throws Exception { QRCodeWriter qrWriter new QRCodeWriter(); HashtableEncodeHintType, Object hints new Hashtable(); hints.put(EncodeHintType.CHARACTER_SET, UTF-8); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); hints.put(EncodeHintType.MARGIN, 1); BitMatrix matrix qrWriter.encode(content, BarcodeFormat.QR_CODE, 100, 100, hints); ByteArrayOutputStream baos new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(matrix, PNG, baos); return data:image/png;base64, java.util.Base64.getEncoder().encodeToString(baos.toByteArray()); } }其中几个细节值得强调Freemarker 初始化建议在静态块中一次性加载模板目录避免每次请求重复构建Configuration。字体注册策略BaseFont.IDENTITY_H支持 Unicode 横向书写适用于中文NOT_EMBEDDED表示不嵌入字体子集节省PDF体积。JBIG 处理机制由于 JBIGKit 底层调用本地命令行工具必须先写入临时.jbig文件再执行转换因此务必做好异常清理。服务层实现CreatePdfService是核心业务入口负责组装数据模型并触发 PDF 生成。Service public class CreatePdfService { private static final Logger log LoggerFactory.getLogger(CreatePdfService.class); public byte[] generatePdf(TxnReceipt receipt) throws Exception { String txnType receipt.getTxnTypeCode(); String templateName Constants.TXN_TEMPLATE_MAP.get(txnType); if (templateName null) { throw new IllegalArgumentException(未找到交易类型对应的模板 txnType); } MapString, Object dataModel buildDataModel(receipt); String html ElecBillCreateUtils.renderHtml(dataModel, templateName); return ElecBillCreateUtils.htmlToPdf(html, Constants.PDF_CONFIG.PDF_FONT_PATH); } private MapString, Object buildDataModel(TxnReceipt receipt) throws Exception { MapString, Object model new HashMap(); model.putAll(receipt.toMap()); // 嵌入头部 Logo String logoBase64 java.util.Base64.getEncoder().encodeToString( readImage(Constants.PDF_CONFIG.PDF_HEADER_LOGO)); model.put(headImg, Constants.PDF_CONFIG.IMG_BASE64_HEADER logoBase64); // 生成二维码 if (receipt.getQrCode() ! null !receipt.getQrCode().isEmpty()) { model.put(qrCode, ElecBillCreateUtils.generateQrCodeBase64(receipt.getQrCode())); } // 处理签名图像 if (receipt.getSignHex() ! null !receipt.getSignHex().isEmpty()) { byte[] jbigBytes ElecBillCreateUtils.hexToBytes(receipt.getSignHex()); String baseDir Constants.PDF_CONFIG.PDF_FILE_PATH; String tempFileName receipt.getOrderId(); ElecBillCreateUtils.jbigToBmp(jbigBytes, baseDir tempFileName); String jpgPath baseDir tempFileName Constants.PDF_CONFIG.JPG_SUFFIX; ElecBillCreateUtils.bmpToJpg(baseDir tempFileName Constants.PDF_CONFIG.BMP_SUFFIX, jpgPath); byte[] jpgBytes readImage(jpgPath); model.put(signBase64, Constants.PDF_CONFIG.IMG_BASE64_HEADER java.util.Base64.getEncoder().encodeToString(jpgBytes)); deleteTempFiles(baseDir, tempFileName); } // 金额格式化 if (model.containsKey(amount)) { Double amt Double.parseDouble(model.get(amount).toString()); model.put(amount, String.format(RMB %,.2f, amt)); } return model; } private byte[] readImage(String path) throws IOException { return org.apache.commons.io.FileUtils.readFileToByteArray(new File(path)); } private void deleteTempFiles(String dir, String name) { Arrays.asList(.jbig, .bmp, .jpg).forEach(ext - { File f new File(dir name ext); if (f.exists()) f.delete(); }); } }这里有几个工程实践建议金额单位处理通常前端传的是“分”为单位的整数应在服务层统一转为“元”并格式化显示空值判断模板中应使用#if var??判断变量是否存在防止空指针异常异步清理对于高频交易系统临时文件删除动作可放入线程池或定时任务中批量执行减轻主线程负担。控制器层接口暴露控制器提供标准 RESTful 接口支持预览与下载两种模式RestController RequestMapping(/pdf) public class PdfGenerateController { Autowired private CreatePdfService pdfService; PostMapping(/generate) public ResponseEntitybyte[] generatePdf(RequestBody TxnReceipt receipt, RequestParam String opFlag) { try { byte[] pdfBytes pdfService.generatePdf(receipt); HttpHeaders headers new HttpHeaders(); String filename receipt.getOrderId() .pdf; if (Constants.PDF_CONFIG.OP_DOWNLOAD.equals(opFlag)) { headers.setContentType(MediaType.APPLICATION_PDF); headers.setContentDispositionFormData(attachment, filename); } else { headers.setContentType(MediaType.APPLICATION_PDF); } return new ResponseEntity(pdfBytes, headers, HttpStatus.OK); } catch (Exception e) { log.error(PDF生成失败, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } }客户端可通过设置opFlag02触发浏览器自动下载而01则用于内嵌预览如 H5 页面 iframe 展示。HTML 模板设计示例模板采用简洁的 HTML 内联 CSS 结构保证样式在 PDF 渲染中的稳定性!DOCTYPE html html head meta charsetUTF-8 style body { font-family: SimHei, sans-serif; font-size: 12px; margin: 20px; } .header { text-align: center; margin-bottom: 10px; } .logo { width: 100px; } .table { width: 100%; border-collapse: collapse; margin-top: 10px; } td { padding: 4px 0; vertical-align: top; } .right { text-align: right; } .dashed { border-bottom: 1px dashed #000; } .center { text-align: center; } .signature-box { height: 60px; border: 1px solid #ccc; margin: 10px 0; } .qrcode { width: 80px; } /style /head body div classheader img src${headImg} altLogo classlogo/ h3贵州农信 · 助农取款凭证/h3 /div table classtable trtd商户名称/tdtd classright${merName}/td/tr trtd操作员/tdtd classright${operatorNo}/td/tr tr classdashedtd卡号/tdtd classright${cardNo?substring(0,4)}****${cardNo?substring(cardNo?length-4)}/td/tr trtd交易时间/tdtd classright${datetime}/td/tr trtd交易金额/tdtd classright${amount}/td/tr /table div classsignature-box strong客户签名/strongbr/ #if signBase64?? img src${signBase64} width120 / /#if /div #if qrCode?? div classcenter img src${qrCode} classqrcode/ div stylefont-size:10px;扫码查询交易记录/div /div /#if div styletext-align:center;margin-top:20px;font-size:10px;color:#666; 请妥善保管此凭证如有疑问请联系客服。 /div /body /htmlFreemarker 支持丰富的表达式语法例如-${cardNo?substring(0,4)}****${cardNo?substring(cardNo?length-4)}实现卡号脱敏-#if signBase64??安全判空防止占位符暴露。性能优化要点优化方向实践建议JBIG 压缩效果实测 BMP 签名从平均 250KB 压至 2.5KB节省 99% 存储空间模板缓存机制Freemarker 自带模板缓存合理配置setTemplateUpdateDelay可提升命中率字体复用simhei.ttf在应用启动时加载一次即可避免重复注册临时文件生命周期控制所有中间文件必须及时清理推荐配合try-finally或 AOP 实现并发处理能力高峰期可引入线程池隔离 PDF 渲染任务防止单点阻塞特别提醒JBIG 解压过程涉及磁盘 IO 和外部命令调用属于相对耗时操作。在 QPS 较高的场景下建议前置压缩步骤由终端完成后端仅做轻量解码。测试验证与部署建议以下是一个典型的单元测试案例Test public void testGeneratePdf() throws Exception { TxnReceipt receipt new TxnReceipt(); receipt.setOrderId(202405200001); receipt.setTxnTypeCode(200401); receipt.setMerName(张三家小卖部); receipt.setOperatorNo(OP001); receipt.setCardNo(6217791234567890); receipt.setDatetime(2024-05-20 14:30:22); receipt.setAmount(15000); receipt.setQrCode(https://api.bank.com/query?id202405200001); receipt.setSignHex(00020100000006560000018c...); // 模拟 Hex 数据 byte[] pdfBytes pdfService.generatePdf(receipt); assertNotNull(pdfBytes); assertTrue(pdfBytes.length 1024); Files.write(Paths.get(test_receipt.pdf), pdfBytes); }部署时建议- 使用 Docker 容器化部署固定字体路径与临时目录- 配置日志监控捕获JBIG.jbg2bmp执行失败等底层异常- 生产环境开启模板缓存并禁用热更新提升性能稳定性。这种融合模板驱动与图像压缩的设计思路已在多个省级农信社项目中成功落地。它不仅解决了传统方案中“大图难存、中文难显、模板难管”的痛点也为未来接入数字签名、区块链存证等功能预留了良好扩展性。随着边缘计算能力的增强甚至可以考虑将部分渲染任务下沉至终端侧进一步释放服务端压力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询