| | |
| | | import com.itextpdf.text.pdf.BaseFont; |
| | | import com.itextpdf.text.pdf.parser.PdfTextExtractor; |
| | | import com.itextpdf.text.pdf.parser.SimpleTextExtractionStrategy; |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.rendering.PDFRenderer; |
| | | import org.apache.pdfbox.rendering.ImageType; |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.awt.Graphics2D; |
| | | import java.awt.RenderingHints; |
| | | import java.awt.Color; |
| | | import java.awt.FontMetrics; |
| | | import java.awt.GraphicsEnvironment; |
| | | import com.nq.pojo.ContractImage; |
| | | import com.nq.dao.ContractImageMapper; |
| | | import com.alibaba.fastjson.JSON; |
| | | |
| | | @Service("iUserAgreementService") |
| | | public class UserAgreementServiceImpl implements IUserAgreementService { |
| | |
| | | |
| | | @Autowired |
| | | private com.nq.service.IUserService iUserService; |
| | | |
| | | @Autowired |
| | | private ContractImageMapper contractImageMapper; |
| | | |
| | | @Override |
| | | |
| | |
| | | return ServerResponse.createByErrorMsg("用户未登录"); |
| | | } |
| | | |
| | | if (StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()) || StringUtils.isBlank(user.getRegAddress())) { |
| | | return ServerResponse.createByErrorMsg("用户信息不完整,请先完成实名认证"); |
| | | if (user.getIsActive()!=2 || StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()) || StringUtils.isBlank(user.getRegAddress())) { |
| | | return ServerResponse.createByErrorMsg("请先完成实名认证"); |
| | | } |
| | | |
| | | // 检查是否已生成PDF |
| | |
| | | dir.mkdirs(); |
| | | } |
| | | |
| | | String pdfFileName = "agreement_" + user.getId() + ".pdf"; |
| | | String pdfFileName = "contract_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(dir, pdfFileName); |
| | | |
| | | // 构建完整的PDF访问URL |
| | | String pdfUrl = PropertiesUtil.getProperty("pdf.server.http.prefix") + "/agreement_" + user.getId() + ".pdf"; |
| | | |
| | | // 如果PDF已存在,删除后重新生成,确保数据是最新的 |
| | | if (pdfFile.exists()) { |
| | |
| | | // 从PDF模板生成新的PDF,替换占位符 |
| | | generatePdfFromTemplate(pdfTemplate, pdfFile, user); |
| | | |
| | | return ServerResponse.createBySuccess(pdfUrl); |
| | | // 将PDF转换为图片并返回图片路径数组 |
| | | List<String> imageUrls = convertPdfToImages(pdfFile, "contract", user.getId()); |
| | | |
| | | return ServerResponse.createBySuccess(imageUrls); |
| | | } catch (Exception e) { |
| | | log.error("生成用户协议PDF失败", e); |
| | | return ServerResponse.createByErrorMsg("生成PDF失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从PDF模板生成新的PDF,替换占位符 |
| | | */ |
| | | private void generatePdfFromTemplate(File templatePdf, File outputPdf, User user) throws Exception { |
| | | log.info("开始生成PDF,模板路径: {}, 输出路径: {}, 用户: {}", |
| | | templatePdf.getAbsolutePath(), outputPdf.getAbsolutePath(), user.getId()); |
| | | |
| | | log.info("开始生成PDF,模板路径: {}, 输出路径: {}, 用户: {}", |
| | | templatePdf.getAbsolutePath(), outputPdf.getAbsolutePath(), user.getId()); |
| | | |
| | | PdfReader reader = null; |
| | | PdfStamper stamper = null; |
| | | FileOutputStream fos = null; |
| | | |
| | | |
| | | try { |
| | | reader = new PdfReader(new FileInputStream(templatePdf)); |
| | | fos = new FileOutputStream(outputPdf); |
| | | stamper = new PdfStamper(reader, fos); |
| | | |
| | | // 设置中文字体 |
| | | BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); |
| | | // 设置中文字体,使用EMBEDDED确保字体嵌入PDF,避免转图片时乱码 |
| | | // 使用itext-asian库提供的中文字体,必须使用EMBEDDED模式 |
| | | BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); |
| | | Font font = new Font(baseFont, 12, Font.NORMAL); |
| | | Font boldFont = new Font(baseFont, 12, Font.BOLD); |
| | | |
| | | log.info("中文字体已加载: {}", baseFont.getPostscriptFontName()); |
| | | |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); |
| | | String currentDate = sdf.format(new Date()); |
| | |
| | | String realName = user.getRealName() != null ? user.getRealName() : ""; |
| | | String idCard = user.getIdCard() != null ? user.getIdCard() : ""; |
| | | String address = user.getRegAddress() != null ? user.getRegAddress() : ""; |
| | | |
| | | |
| | | log.info("PDF生成数据 - 姓名: {}, 身份证: {}, 地址: {}", realName, idCard, address); |
| | | |
| | | // 获取PDF页数 |
| | |
| | | reader = null; |
| | | fos.close(); |
| | | fos = null; |
| | | |
| | | |
| | | // 验证生成的文件 |
| | | if (outputPdf.exists()) { |
| | | long fileSize = outputPdf.length(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 在PDF上替换占位符(使用文本覆盖方式) |
| | | * 在PDF上替换占位符(使用透明背景图片方式) |
| | | */ |
| | | private void replacePlaceholdersInPdf(PdfContentByte canvas, Rectangle pageSize, String pageText, |
| | | String realName, String idCard, String address, |
| | | String currentDate, Font font,int pageNum) throws Exception { |
| | | // 查找占位符在文本中的位置,然后估算在PDF中的坐标 |
| | | // 由于PDF坐标系统复杂,这里使用固定位置替换 |
| | | // 实际使用时需要根据PDF模板的具体布局调整坐标 |
| | | String realName, String idCard, String address, |
| | | String currentDate, Font font,int pageNum) throws Exception { |
| | | float margin = 50; |
| | | float lineHeight = 14; |
| | | float startY = pageSize.getHeight() - 194; |
| | | float startY = pageSize.getHeight() - 204; |
| | | |
| | | if(pageNum == 1){ |
| | | replacePlaceholder(canvas, "", realName, margin + 75, startY, 0, font); |
| | | // 生成透明背景的姓名图片并插入 |
| | | File nameImage = createTextImage(realName, font); |
| | | insertImageToPdf(canvas, nameImage, margin + 75, startY); |
| | | startY -= lineHeight; |
| | | |
| | | replacePlaceholder(canvas, "", address, margin + 75, startY-2, 0, font); |
| | | // 生成透明背景的地址图片并插入 |
| | | File addressImage = createTextImage(address, font); |
| | | insertImageToPdf(canvas, addressImage, margin + 75, startY-2); |
| | | startY -= lineHeight; |
| | | |
| | | replacePlaceholder(canvas, "", idCard, margin + 95, startY-2, 0, font); |
| | | // 生成透明背景的身份证号图片并插入 |
| | | File idCardImage = createTextImage(idCard, font); |
| | | insertImageToPdf(canvas, idCardImage, margin + 95, startY-2); |
| | | } |
| | | if(pageNum == 3){ |
| | | replacePlaceholder(canvas, "", currentDate, margin + 60, startY+32, 0, font); |
| | | replacePlaceholder(canvas, "", currentDate, margin + 60, startY, 0, font); |
| | | // 生成透明背景的日期图片并插入 |
| | | File dateImage1 = createTextImage(currentDate, font); |
| | | insertImageToPdf(canvas, dateImage1, margin + 60, startY+32); |
| | | |
| | | File dateImage2 = createTextImage(currentDate, font); |
| | | insertImageToPdf(canvas, dateImage2, margin + 60, startY); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 替换单个占位符 |
| | | * 从资源文件加载字体 |
| | | */ |
| | | private void replacePlaceholder(PdfContentByte canvas, String placeholder, String value, |
| | | float x, float y, float width, Font font) { |
| | | // 添加白色背景覆盖占位符区域 |
| | | canvas.saveState(); |
| | | canvas.setColorFill(BaseColor.WHITE); |
| | | canvas.rectangle(x - 5, y - 15, width, 20); |
| | | canvas.fill(); |
| | | canvas.restoreState(); |
| | | private java.awt.Font loadFontFromResource(String fontPath, int size) { |
| | | try { |
| | | InputStream fontStream = this.getClass().getClassLoader().getResourceAsStream(fontPath); |
| | | if (fontStream != null) { |
| | | java.awt.Font font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, fontStream); |
| | | font = font.deriveFont(java.awt.Font.PLAIN, size); |
| | | fontStream.close(); |
| | | log.info("成功从资源文件加载字体: {}", fontPath); |
| | | return font; |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("从资源文件加载字体失败: {}, 错误: {}", fontPath, e.getMessage()); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // 添加新文本 |
| | | ColumnText.showTextAligned(canvas, Element.ALIGN_LEFT, |
| | | new Phrase(value, font), x, y, 0); |
| | | /** |
| | | * 从系统字体目录加载字体文件 |
| | | */ |
| | | private java.awt.Font loadFontFromFile(String fontPath, int size) { |
| | | try { |
| | | File fontFile = new File(fontPath); |
| | | if (fontFile.exists()) { |
| | | java.awt.Font font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, fontFile); |
| | | font = font.deriveFont(java.awt.Font.PLAIN, size); |
| | | log.info("成功从文件加载字体: {}", fontPath); |
| | | return font; |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("从文件加载字体失败: {}, 错误: {}", fontPath, e.getMessage()); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 获取可用的中文字体 |
| | | */ |
| | | private java.awt.Font getChineseFont(int size) { |
| | | // 1. 优先从资源文件加载字体(如果项目中有字体文件) |
| | | java.awt.Font font = loadFontFromResource("fonts/simsun.ttf", size); |
| | | if (font != null) return font; |
| | | |
| | | font = loadFontFromResource("fonts/simhei.ttf", size); |
| | | if (font != null) return font; |
| | | |
| | | // 2. 从系统字体目录加载(Linux常见字体路径) |
| | | String[] systemFontPaths = { |
| | | "/usr/share/fonts/chinese/simsun.ttf", // CentOS/RHEL |
| | | "/usr/share/fonts/truetype/wqy/wqy-microhei.ttf", // 文泉驿 |
| | | "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", // Noto字体 |
| | | "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", // DejaVu |
| | | "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", // Liberation |
| | | "/usr/share/fonts/chinese/SimSun.ttf", |
| | | "/usr/share/fonts/chinese/SimHei.ttf", |
| | | "/usr/share/fonts/truetype/arphic/uming.ttc", // 文鼎字体 |
| | | "/usr/share/fonts/truetype/arphic/ukai.ttc" |
| | | }; |
| | | |
| | | for (String fontPath : systemFontPaths) { |
| | | font = loadFontFromFile(fontPath, size); |
| | | if (font != null) return font; |
| | | } |
| | | |
| | | // 3. 按优先级尝试加载系统已安装的字体 |
| | | String[] fontNames = { |
| | | "SimSun", // 宋体 (Windows) |
| | | "SimHei", // 黑体 (Windows) |
| | | "Microsoft YaHei", // 微软雅黑 (Windows) |
| | | "STSong", // 华文宋体 |
| | | "STHeiti", // 华文黑体 |
| | | "WenQuanYi Micro Hei", // 文泉驿微米黑 (Linux) |
| | | "WenQuanYi Zen Hei", // 文泉驿正黑 (Linux) |
| | | "Noto Sans CJK SC", // Noto字体 (Linux) |
| | | "Droid Sans Fallback", // Android字体 (Linux) |
| | | "AR PL UMing CN", // 文鼎字体 (Linux) |
| | | "AR PL UKai CN" // 文鼎字体 (Linux) |
| | | }; |
| | | |
| | | GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
| | | String[] availableFonts = ge.getAvailableFontFamilyNames(); |
| | | |
| | | // 查找可用的中文字体 |
| | | for (String fontName : fontNames) { |
| | | for (String availableFont : availableFonts) { |
| | | if (availableFont.equals(fontName)) { |
| | | try { |
| | | font = new java.awt.Font(fontName, java.awt.Font.PLAIN, size); |
| | | log.info("成功加载系统字体: {}", fontName); |
| | | return font; |
| | | } catch (Exception e) { |
| | | log.warn("加载字体{}失败: {}", fontName, e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 如果找不到中文字体,尝试使用逻辑字体 |
| | | log.warn("未找到可用的中文字体,使用SERIF逻辑字体,可能出现乱码"); |
| | | return new java.awt.Font(java.awt.Font.SERIF, java.awt.Font.PLAIN, size); |
| | | } |
| | | |
| | | /** |
| | | * 创建透明背景的文本图片 |
| | | */ |
| | | private File createTextImage(String text, Font font) throws Exception { |
| | | if (StringUtils.isBlank(text)) { |
| | | text = ""; |
| | | } |
| | | |
| | | // 创建临时文件 |
| | | String tempDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | File dir = new File(tempDir); |
| | | if (!dir.exists()) { |
| | | dir.mkdirs(); |
| | | } |
| | | |
| | | String tempFileName = "text_" + System.currentTimeMillis() + "_" + Thread.currentThread().getId() + ".png"; |
| | | File imageFile = new File(dir, tempFileName); |
| | | |
| | | // 创建透明背景的BufferedImage (TYPE_INT_ARGB 默认透明) |
| | | BufferedImage image = new BufferedImage(500, 50, BufferedImage.TYPE_INT_ARGB); |
| | | Graphics2D g2d = image.createGraphics(); |
| | | |
| | | // 启用透明背景支持 |
| | | g2d.setComposite(java.awt.AlphaComposite.SrcOver); |
| | | |
| | | // 设置高质量渲染 |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); |
| | | g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); |
| | | g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); |
| | | |
| | | // 使用可用的中文字体 |
| | | java.awt.Font awtFont = getChineseFont(12); |
| | | g2d.setFont(awtFont); |
| | | |
| | | // 使用深黑色,RGB(0, 0, 0) 完全不透明 |
| | | g2d.setColor(new Color(0, 0, 0, 255)); |
| | | |
| | | // 绘制文本 |
| | | FontMetrics fm = g2d.getFontMetrics(); |
| | | int textWidth = fm.stringWidth(text); |
| | | int textHeight = fm.getHeight(); |
| | | |
| | | // 调整图片大小以适应文本 |
| | | if (textWidth > 0 && textHeight > 0) { |
| | | // TYPE_INT_ARGB 类型默认透明背景,不需要清除 |
| | | BufferedImage resizedImage = new BufferedImage(textWidth + 20, textHeight + 10, BufferedImage.TYPE_INT_ARGB); |
| | | Graphics2D g2dResized = resizedImage.createGraphics(); |
| | | |
| | | // 启用透明背景支持 |
| | | g2dResized.setComposite(java.awt.AlphaComposite.SrcOver); |
| | | |
| | | // 设置高质量渲染 |
| | | g2dResized.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | g2dResized.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); |
| | | g2dResized.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); |
| | | g2dResized.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); |
| | | |
| | | // 使用相同的中文字体 |
| | | g2dResized.setFont(awtFont); |
| | | // 使用深黑色,RGB(0, 0, 0) 完全不透明 |
| | | g2dResized.setColor(new Color(0, 0, 0, 255)); |
| | | |
| | | // 绘制文本 |
| | | g2dResized.drawString(text, 10, textHeight); |
| | | g2dResized.dispose(); |
| | | |
| | | // 保存图片 |
| | | ImageIO.write(resizedImage, "PNG", imageFile); |
| | | image = resizedImage; |
| | | } else { |
| | | // 如果文本为空,创建一个小的透明图片 |
| | | BufferedImage emptyImage = new BufferedImage(50, 20, BufferedImage.TYPE_INT_ARGB); |
| | | ImageIO.write(emptyImage, "PNG", imageFile); |
| | | image = emptyImage; |
| | | } |
| | | |
| | | g2d.dispose(); |
| | | |
| | | log.info("已创建透明背景文本图片: {}, 文本: {}", imageFile.getAbsolutePath(), text); |
| | | return imageFile; |
| | | } |
| | | |
| | | /** |
| | | * 将图片插入到PDF指定位置 |
| | | */ |
| | | private void insertImageToPdf(PdfContentByte canvas, File imageFile, float x, float y) throws Exception { |
| | | if (imageFile == null || !imageFile.exists()) { |
| | | log.warn("图片文件不存在: {}", imageFile); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | Image image = Image.getInstance(imageFile.getAbsolutePath()); |
| | | image.setAbsolutePosition(x, y); |
| | | canvas.addImage(image); |
| | | log.info("图片已插入到PDF,位置: ({}, {})", x, y); |
| | | |
| | | // 删除临时图片文件 |
| | | try { |
| | | imageFile.delete(); |
| | | } catch (Exception e) { |
| | | log.warn("删除临时图片文件失败: {}", imageFile.getAbsolutePath(), e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("插入图片到PDF失败: {}", imageFile.getAbsolutePath(), e); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void viewAgreementPdf(HttpServletRequest request, HttpServletResponse response) { |
| | | FileInputStream fis = null; |
| | | OutputStream os = null; |
| | | public ServerResponse viewAgreementPdf(HttpServletRequest request) { |
| | | try { |
| | | User user = iUserService.getCurrentUser(request); |
| | | if (user == null) { |
| | | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "用户未登录"); |
| | | return; |
| | | return ServerResponse.createByErrorMsg("用户未登录"); |
| | | } |
| | | |
| | | String pdfDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | String pdfFileName = "agreement_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(pdfDir, pdfFileName); |
| | | |
| | | log.info("查看PDF文件,路径: {}, 是否存在: {}, 文件大小: {}", |
| | | pdfFile.getAbsolutePath(), pdfFile.exists(), |
| | | pdfFile.exists() ? pdfFile.length() : 0); |
| | | |
| | | if (!pdfFile.exists()) { |
| | | response.sendError(HttpServletResponse.SC_NOT_FOUND, "协议文件不存在,请先生成协议"); |
| | | return; |
| | | // 从数据库查询已签名的图片 |
| | | List<ContractImage> contractImages = contractImageMapper.selectByUserIdAndType(user.getId(), "contract"); |
| | | List<String> imageUrls = new ArrayList<>(); |
| | | |
| | | if (contractImages != null && !contractImages.isEmpty()) { |
| | | for (ContractImage image : contractImages) { |
| | | imageUrls.add(image.getAddress()); |
| | | } |
| | | log.info("从数据库查询到用户{}的{}张已签名合同图片", user.getId(), imageUrls.size()); |
| | | } |
| | | |
| | | long fileLength = pdfFile.length(); |
| | | if (fileLength == 0) { |
| | | log.error("PDF文件大小为0: {}", pdfFile.getAbsolutePath()); |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF文件为空"); |
| | | return; |
| | | } |
| | | |
| | | // 验证PDF文件头 |
| | | FileInputStream checkFis = new FileInputStream(pdfFile); |
| | | byte[] header = new byte[4]; |
| | | checkFis.read(header); |
| | | checkFis.close(); |
| | | String headerStr = new String(header); |
| | | if (!headerStr.startsWith("%PDF")) { |
| | | log.error("文件不是有效的PDF格式,文件头: {}", headerStr); |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF文件格式错误"); |
| | | return; |
| | | } |
| | | log.info("PDF文件头验证通过: {}", headerStr); |
| | | |
| | | response.setContentType("application/pdf"); |
| | | response.setHeader("Content-Disposition", "inline; filename=\"" + |
| | | new String(pdfFileName.getBytes("UTF-8"), "ISO-8859-1") + "\""); |
| | | response.setContentLengthLong(fileLength); |
| | | response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); |
| | | response.setHeader("Pragma", "no-cache"); |
| | | response.setDateHeader("Expires", 0); |
| | | |
| | | fis = new FileInputStream(pdfFile); |
| | | os = response.getOutputStream(); |
| | | |
| | | byte[] buffer = new byte[8192]; |
| | | int bytesRead; |
| | | long totalBytes = 0; |
| | | while ((bytesRead = fis.read(buffer)) != -1) { |
| | | os.write(buffer, 0, bytesRead); |
| | | totalBytes += bytesRead; |
| | | } |
| | | |
| | | log.info("PDF文件传输完成,总字节数: {}", totalBytes); |
| | | os.flush(); |
| | | return ServerResponse.createBySuccess(imageUrls); |
| | | } catch (Exception e) { |
| | | log.error("查看用户协议PDF失败", e); |
| | | try { |
| | | if (!response.isCommitted()) { |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "查看PDF失败: " + e.getMessage()); |
| | | } |
| | | } catch (IOException ex) { |
| | | log.error("发送错误响应失败", ex); |
| | | } |
| | | } finally { |
| | | if (fis != null) { |
| | | try { |
| | | fis.close(); |
| | | } catch (IOException e) { |
| | | log.error("关闭文件流失败", e); |
| | | } |
| | | } |
| | | if (os != null) { |
| | | try { |
| | | os.close(); |
| | | } catch (IOException e) { |
| | | log.error("关闭输出流失败", e); |
| | | } |
| | | } |
| | | return ServerResponse.createByErrorMsg("查看PDF失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | |
| | | return ServerResponse.createByErrorMsg("签名图片不能为空"); |
| | | } |
| | | |
| | | // 保存签名图片到本地 |
| | | String signatureDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | File dir = new File(signatureDir); |
| | | if (!dir.exists()) { |
| | |
| | | |
| | | String signatureFileName = "signature_" + user.getId() + ".png"; |
| | | File signatureFileLocal = new File(dir, signatureFileName); |
| | | |
| | | |
| | | signatureFile.transferTo(signatureFileLocal); |
| | | |
| | | |
| | | log.info("用户{}的签名图片已保存: {}", user.getId(), signatureFileLocal.getAbsolutePath()); |
| | | |
| | | // 重新生成PDF并插入签名图片 |
| | | |
| | | String pdfDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | String pdfFileName = "agreement_" + user.getId() + ".pdf"; |
| | | String pdfFileName = "contract_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(pdfDir, pdfFileName); |
| | | |
| | | //删之前的图片 |
| | | deleteFilesWithPattern(pdfDir,"contract_" + user.getId()); |
| | | if (pdfFile.exists()) { |
| | | // 在PDF第三页插入签名图片 |
| | | addSignatureToPdf(pdfFile, signatureFileLocal, user); |
| | | |
| | | // 构建PDF访问地址 |
| | | String pdfUrl = PropertiesUtil.getProperty("pdf.server.http.prefix") + "/agreement_" + user.getId() + ".pdf"; |
| | | |
| | | // 更新用户表的签合同标记(保存PDF地址) |
| | | iUserService.updateSignedContract(user.getId(),pdfUrl); |
| | | List<String> imageUrls = convertPdfToImages(pdfFile, "contract", user.getId()); |
| | | for(String imageUrl : imageUrls) { |
| | | ContractImage contractImage = new ContractImage(); |
| | | contractImage.setUserId(user.getId()); |
| | | contractImage.setContractType("contract"); |
| | | contractImage.setAddress(imageUrl); |
| | | contractImage.setAddTime(new Date()); |
| | | contractImageMapper.insert(contractImage); |
| | | } |
| | | iUserService.updateSignedContract(user.getId(), "Y"); |
| | | } |
| | | return ServerResponse.createBySuccessMsg("签名保存成功"); |
| | | } catch (Exception e) { |
| | |
| | | PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(tempPdf)); |
| | | |
| | | int pageCount = reader.getNumberOfPages(); |
| | | |
| | | |
| | | // 在第三页插入签名图片 |
| | | if (pageCount >= 3) { |
| | | PdfContentByte canvas = stamper.getOverContent(3); |
| | | Rectangle pageSize = reader.getPageSize(3); |
| | | |
| | | |
| | | // 添加签名图片 |
| | | if (signatureImage.exists()) { |
| | | Image image = Image.getInstance(signatureImage.getAbsolutePath()); |
| | |
| | | |
| | | stamper.close(); |
| | | reader.close(); |
| | | |
| | | |
| | | // 替换原PDF文件 |
| | | if (tempPdf.exists()) { |
| | | pdfFile.delete(); |
| | |
| | | return ServerResponse.createByErrorMsg("用户未登录"); |
| | | } |
| | | |
| | | if (StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()) || StringUtils.isBlank(user.getRegAddress())) { |
| | | if (user.getIsActive()!=2 || StringUtils.isBlank(user.getRealName()) || StringUtils.isBlank(user.getIdCard()) || StringUtils.isBlank(user.getRegAddress())) { |
| | | return ServerResponse.createByErrorMsg("用户信息不完整,请先完成实名认证"); |
| | | } |
| | | |
| | |
| | | dir.mkdirs(); |
| | | } |
| | | |
| | | String pdfFileName = "contract_" + user.getId() + ".pdf"; |
| | | String pdfFileName = "agreement_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(dir, pdfFileName); |
| | | |
| | | String pdfUrl = PropertiesUtil.getProperty("pdf.server.http.prefix") + "/contract_" + user.getId() + ".pdf"; |
| | | |
| | | if (pdfFile.exists()) { |
| | | boolean deleted = pdfFile.delete(); |
| | |
| | | |
| | | generateContractPdfFromTemplate(pdfTemplate, pdfFile, user); |
| | | |
| | | return ServerResponse.createBySuccess(pdfUrl); |
| | | // 将PDF转换为图片并返回图片路径数组 |
| | | List<String> imageUrls = convertPdfToImages(pdfFile, "agreement", user.getId()); |
| | | |
| | | return ServerResponse.createBySuccess(imageUrls); |
| | | } catch (Exception e) { |
| | | log.error("生成用户合同PDF失败", e); |
| | | return ServerResponse.createByErrorMsg("生成PDF失败:" + e.getMessage()); |
| | |
| | | |
| | | @Override |
| | | public void viewAgreementContractPdf(HttpServletRequest request, HttpServletResponse response) { |
| | | FileInputStream fis = null; |
| | | OutputStream os = null; |
| | | try { |
| | | User user = iUserService.getCurrentUser(request); |
| | | if (user == null) { |
| | |
| | | return; |
| | | } |
| | | |
| | | String pdfDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | String pdfFileName = "contract_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(pdfDir, pdfFileName); |
| | | |
| | | if (!pdfFile.exists()) { |
| | | response.sendError(HttpServletResponse.SC_NOT_FOUND, "合同文件不存在,请先生成合同"); |
| | | return; |
| | | // 从数据库查询已签名的图片 |
| | | List<ContractImage> contractImages = contractImageMapper.selectByUserIdAndType(user.getId(), "agreement"); |
| | | List<String> imageUrls = new ArrayList<>(); |
| | | |
| | | if (contractImages != null && !contractImages.isEmpty()) { |
| | | for (ContractImage image : contractImages) { |
| | | imageUrls.add(image.getAddress()); |
| | | } |
| | | log.info("从数据库查询到用户{}的{}张已签名保密协议图片", user.getId(), imageUrls.size()); |
| | | } |
| | | |
| | | long fileLength = pdfFile.length(); |
| | | if (fileLength == 0) { |
| | | log.error("PDF文件大小为0: {}", pdfFile.getAbsolutePath()); |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF文件为空"); |
| | | return; |
| | | } |
| | | |
| | | FileInputStream checkFis = new FileInputStream(pdfFile); |
| | | byte[] header = new byte[4]; |
| | | checkFis.read(header); |
| | | checkFis.close(); |
| | | String headerStr = new String(header); |
| | | if (!headerStr.startsWith("%PDF")) { |
| | | log.error("文件不是有效的PDF格式,文件头: {}", headerStr); |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF文件格式错误"); |
| | | return; |
| | | } |
| | | |
| | | response.setContentType("application/pdf"); |
| | | response.setHeader("Content-Disposition", "inline; filename=\"" + |
| | | new String(pdfFileName.getBytes("UTF-8"), "ISO-8859-1") + "\""); |
| | | response.setContentLengthLong(fileLength); |
| | | response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); |
| | | |
| | | fis = new FileInputStream(pdfFile); |
| | | os = response.getOutputStream(); |
| | | byte[] buffer = new byte[4096]; |
| | | int bytesRead; |
| | | while ((bytesRead = fis.read(buffer)) != -1) { |
| | | os.write(buffer, 0, bytesRead); |
| | | } |
| | | os.flush(); |
| | | response.setContentType("application/json;charset=UTF-8"); |
| | | response.getWriter().write(JSON.toJSONString(ServerResponse.createBySuccess(imageUrls))); |
| | | response.getWriter().flush(); |
| | | } catch (Exception e) { |
| | | log.error("查看用户合同PDF失败", e); |
| | | try { |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "查看PDF失败: " + e.getMessage()); |
| | | if (!response.isCommitted()) { |
| | | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "查看PDF失败: " + e.getMessage()); |
| | | } |
| | | } catch (IOException ex) { |
| | | log.error("发送错误响应失败", ex); |
| | | } |
| | | } finally { |
| | | if (fis != null) { |
| | | try { |
| | | fis.close(); |
| | | } catch (IOException e) { |
| | | log.error("关闭文件流失败", e); |
| | | } |
| | | } |
| | | if (os != null) { |
| | | try { |
| | | os.close(); |
| | | } catch (IOException e) { |
| | | log.error("关闭输出流失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | dir.mkdirs(); |
| | | } |
| | | |
| | | String signatureFileName = "contract_signature_" + user.getId() + ".png"; |
| | | String signatureFileName = "agreement_signature_" + user.getId() + ".png"; |
| | | File signatureFileLocal = new File(dir, signatureFileName); |
| | | |
| | | |
| | | signatureFile.transferTo(signatureFileLocal); |
| | | |
| | | |
| | | log.info("用户{}的合同签名图片已保存: {}", user.getId(), signatureFileLocal.getAbsolutePath()); |
| | | |
| | | |
| | | String pdfDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | String pdfFileName = "contract_" + user.getId() + ".pdf"; |
| | | String pdfFileName = "agreement_" + user.getId() + ".pdf"; |
| | | File pdfFile = new File(pdfDir, pdfFileName); |
| | | |
| | | //删之前的图片 |
| | | deleteFilesWithPattern(pdfDir,"agreement_" + user.getId()); |
| | | if (pdfFile.exists()) { |
| | | addSignatureToContractPdf(pdfFile, signatureFileLocal, user); |
| | | |
| | | String pdfUrl = PropertiesUtil.getProperty("pdf.server.http.prefix") + "/contract_" + user.getId() + ".pdf"; |
| | | |
| | | iUserService.updateSignedAgreement(user.getId(), pdfUrl); |
| | | List<String> imageUrls = convertPdfToImages(pdfFile, "agreement", user.getId()); |
| | | for (String imageUrl : imageUrls) { |
| | | ContractImage contractImage = new ContractImage(); |
| | | contractImage.setUserId(user.getId()); |
| | | contractImage.setContractType("agreement"); |
| | | contractImage.setAddress(imageUrl); |
| | | contractImage.setAddTime(new Date()); |
| | | contractImageMapper.insert(contractImage); |
| | | } |
| | | iUserService.updateSignedAgreement(user.getId(), "Y"); |
| | | } |
| | | |
| | | |
| | | return ServerResponse.createBySuccessMsg("签名保存成功"); |
| | | } catch (Exception e) { |
| | | log.error("保存用户合同签名失败", e); |
| | |
| | | PdfReader reader = new PdfReader(new FileInputStream(templatePdf)); |
| | | PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(outputPdf)); |
| | | |
| | | BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); |
| | | // 设置中文字体,使用EMBEDDED确保字体嵌入PDF,避免转图片时乱码 |
| | | BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); |
| | | Font font = new Font(baseFont, 12, Font.NORMAL); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日"); |
| | | String currentDate = sdf.format(new Date()); |
| | | |
| | | |
| | | int pageCount = reader.getNumberOfPages(); |
| | | |
| | | for (int pageNum = 1; pageNum <= pageCount; pageNum++) { |
| | |
| | | |
| | | private void replacePlaceholdersInContractPdf(PdfContentByte canvas, Rectangle pageSize, String currentDate, Font font, int pageNum) throws Exception { |
| | | float margin = 50; |
| | | float startY = pageSize.getHeight() - 364; |
| | | float startY = pageSize.getHeight() - 374; |
| | | if(pageNum == 2){ |
| | | replacePlaceholder(canvas, "", currentDate, margin + 320, startY, 0, font); |
| | | // 生成透明背景的日期图片并插入 |
| | | File dateImage = createTextImage(currentDate, font); |
| | | insertImageToPdf(canvas, dateImage, margin + 320, startY); |
| | | } |
| | | } |
| | | |
| | |
| | | PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(tempPdf)); |
| | | |
| | | int pageCount = reader.getNumberOfPages(); |
| | | |
| | | |
| | | if (pageCount >= 2) { |
| | | PdfContentByte canvas = stamper.getOverContent(2); |
| | | Rectangle pageSize = reader.getPageSize(2); |
| | | |
| | | |
| | | if (signatureImage.exists()) { |
| | | Image image = Image.getInstance(signatureImage.getAbsolutePath()); |
| | | image.scaleToFit(100, 100); |
| | |
| | | |
| | | stamper.close(); |
| | | reader.close(); |
| | | |
| | | |
| | | if (tempPdf.exists()) { |
| | | pdfFile.delete(); |
| | | tempPdf.renameTo(pdfFile); |
| | |
| | | log.error("PDF模板目录配置不存在: pdf.template.dir"); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | File dir = new File(templateDir); |
| | | if (!dir.exists()) { |
| | | log.error("PDF模板目录不存在: {}", templateDir); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | File templateFile = new File(dir, templateFileName); |
| | | if (!templateFile.exists()) { |
| | | log.error("PDF模板文件不存在: {}", templateFile.getAbsolutePath()); |
| | | return null; |
| | | } |
| | | |
| | | |
| | | log.info("使用模板文件: {}", templateFile.getAbsolutePath()); |
| | | return templateFile; |
| | | } catch (Exception e) { |
| | |
| | | return null; |
| | | } |
| | | } |
| | | /** |
| | | * 将PDF转换为图片 |
| | | */ |
| | | private List<String> convertPdfToImages(File pdfFile, String contractType, Integer userId) throws Exception { |
| | | List<String> imageUrls = new ArrayList<>(); |
| | | String imagePrefix = PropertiesUtil.getProperty("pdf.server.http.prefix"); |
| | | String pdfDir = PropertiesUtil.getProperty("loca.pdf.dir"); |
| | | |
| | | long timestamp = System.currentTimeMillis(); |
| | | PDDocument document = null; |
| | | try { |
| | | document = PDDocument.load(pdfFile); |
| | | |
| | | // 创建PDFRenderer,使用高质量渲染 |
| | | PDFRenderer pdfRenderer = new PDFRenderer(document); |
| | | |
| | | int pageCount = document.getNumberOfPages(); |
| | | log.info("PDF总页数: {}", pageCount); |
| | | |
| | | for (int page = 0; page < pageCount; page++) { |
| | | // 使用RGB模式渲染,DPI设置为300确保文字清晰 |
| | | // ImageType.RGB可以更好地处理中文字体 |
| | | BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); |
| | | |
| | | // 对图片进行优化处理,确保文字清晰 |
| | | BufferedImage optimizedImage = optimizeImage(image); |
| | | |
| | | String imageFileName = contractType + "_" +userId +"_" + timestamp + ".png"; |
| | | if (page > 0) { |
| | | imageFileName = contractType+ "_" + userId + "_" + timestamp + "_" + (page + 1) + ".png"; |
| | | } |
| | | File imageFile = new File(pdfDir, imageFileName); |
| | | |
| | | ImageIO.write(optimizedImage, "png", imageFile); |
| | | |
| | | String imageUrl = imagePrefix + "/" + imageFileName; |
| | | imageUrls.add(imageUrl); |
| | | |
| | | log.info("PDF第{}页已转换为图片: {}", page + 1, imageUrl); |
| | | } |
| | | } finally { |
| | | if (document != null) { |
| | | document.close(); |
| | | } |
| | | } |
| | | |
| | | return imageUrls; |
| | | } |
| | | |
| | | /** |
| | | * 优化图片,确保文字清晰 |
| | | */ |
| | | private BufferedImage optimizeImage(BufferedImage originalImage) { |
| | | int width = originalImage.getWidth(); |
| | | int height = originalImage.getHeight(); |
| | | |
| | | BufferedImage optimizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
| | | Graphics2D g2d = optimizedImage.createGraphics(); |
| | | |
| | | // 设置高质量渲染 |
| | | g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
| | | g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
| | | g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); |
| | | |
| | | g2d.drawImage(originalImage, 0, 0, null); |
| | | g2d.dispose(); |
| | | |
| | | return optimizedImage; |
| | | } |
| | | |
| | | @Override |
| | | public ServerResponse getContractImages(HttpServletRequest request) { |
| | | try { |
| | | User user = iUserService.getCurrentUser(request); |
| | | if (user == null) { |
| | | return ServerResponse.createByErrorMsg("用户未登录"); |
| | | } |
| | | |
| | | List<ContractImage> images = contractImageMapper.selectByUserIdAndType(user.getId(), "contract"); |
| | | List<String> imageUrls = new ArrayList<>(); |
| | | for (ContractImage image : images) { |
| | | imageUrls.add(image.getAddress()); |
| | | } |
| | | |
| | | return ServerResponse.createBySuccess(imageUrls); |
| | | } catch (Exception e) { |
| | | log.error("查询合同图片列表失败", e); |
| | | return ServerResponse.createByErrorMsg("查询失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public ServerResponse getAgreementImages(HttpServletRequest request) { |
| | | try { |
| | | User user = iUserService.getCurrentUser(request); |
| | | if (user == null) { |
| | | return ServerResponse.createByErrorMsg("用户未登录"); |
| | | } |
| | | |
| | | List<ContractImage> images = contractImageMapper.selectByUserIdAndType(user.getId(), "agreement"); |
| | | List<String> imageUrls = new ArrayList<>(); |
| | | for (ContractImage image : images) { |
| | | imageUrls.add(image.getAddress()); |
| | | } |
| | | |
| | | return ServerResponse.createBySuccess(imageUrls); |
| | | } catch (Exception e) { |
| | | log.error("查询保密协议图片列表失败", e); |
| | | return ServerResponse.createByErrorMsg("查询失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | public void deleteFilesWithPattern(String dirPath, String userId) { |
| | | File directory = new File(dirPath); |
| | | if (!directory.exists() || !directory.isDirectory()) { |
| | | System.out.println("目录不存在或路径错误: " + dirPath); |
| | | return; |
| | | } |
| | | |
| | | File[] files = directory.listFiles((dir, name) -> name.contains(userId + "_")); |
| | | if (files != null) { |
| | | for (File file : files) { |
| | | if (file.delete()) { |
| | | System.out.println("已删除文件: " + file.getName()); |
| | | } else { |
| | | System.out.println("删除失败: " + file.getName()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |