Преглед изворни кода

add:添加公章和水印

zoie пре 2 недеља
родитељ
комит
c46cb6cb40
8 измењених фајлова са 414 додато и 0 уклоњено
  1. 124 0
      .gitignore
  2. 74 0
      add_signature.py
  3. 44 0
      add_watermark.py
  4. 103 0
      app.py
  5. 34 0
      lib/Qiniu.py
  6. 35 0
      requirements.txt
  7. BIN
      static/watermark.pdf
  8. BIN
      static/报告专用章.png

+ 124 - 0
.gitignore

@@ -0,0 +1,124 @@
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# VisualStudioCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# local
+.idea
+/_
+/.cache
+.vscode/
+./temp
+__pycache__/

+ 74 - 0
add_signature.py

@@ -0,0 +1,74 @@
+import io
+import tempfile
+from reportlab.lib.pagesizes import A4
+import pdfplumber
+from PyPDF2 import PdfReader, PdfWriter
+from reportlab.pdfgen import canvas
+from PIL import Image
+import logging
+
+# 配置日志
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+def find_signature_positions(pdf_path, target_texts=["签字或盖章", "技术支持单位(盖章)"]):
+    """查找所有签名位置"""
+    positions = []
+    with pdfplumber.open(pdf_path) as pdf:
+        for i, page in enumerate(pdf.pages):
+            for word in page.extract_words():
+                for target in target_texts:
+                    if target in word["text"]:
+                        x, y = word["x0"], word["top"]
+                        positions.append((i, x, y, target))
+                        logger.info(f"找到签名位置: 第{i}页, {target} at ({x}, {y})")
+
+    # 返回结果:所有"签字或盖章"和"技术支持单位(盖章)"的位置
+    if len(positions) == 0 :
+        return None
+        # 返回结果:第二个"签字或盖章"和技术支持单位位置
+    result = []
+    if len(positions) >= 2:
+        result.append(positions[1])
+        # 第二个出现的位置(索引1)
+    elif positions is not None:
+        result.append(positions)
+
+    return result
+
+
+
+
+def add_signature_to_pdf(input_pdf, output_pdf, signature_img, positions, offset_x=30, offset_y=-10):
+    """添加签名到PDF"""
+    reader = PdfReader(input_pdf)
+    writer = PdfWriter()
+    a4_width, a4_height = A4  # (595.2, 841.68)
+    for page_index in range(len(reader.pages)):
+        page = reader.pages[page_index]
+        for pos in positions:
+            if pos[0] == page_index:
+                packet = io.BytesIO()
+                can = canvas.Canvas(packet, pagesize=A4)
+                img = Image.open(signature_img)
+                img_width, img_height = img.size
+                aspect_ratio = img_height / img_width
+                new_width = 43 * (72 / 25.4)  # 40mm ≈ 113.39点
+                new_height = new_width * aspect_ratio
+                x, y = pos[1], a4_height - pos[2]
+                if pos[3] == "技术支持单位(盖章)":
+                    y = y - new_height / 2
+                else:
+                    y = y - new_height / 3 * 2
+                x = max(0, min(x, a4_width - new_width))
+                y = max(0, min(y, a4_height - new_height))
+                can.drawImage(signature_img, x, y, width=new_width, height=new_height, mask='auto')
+                can.save()
+                packet.seek(0)
+                overlay_pdf = PdfReader(packet)
+                page.merge_page(overlay_pdf.pages[0])
+        writer.add_page(page)
+
+    with open(output_pdf, "wb") as f:
+        writer.write(f)

+ 44 - 0
add_watermark.py

@@ -0,0 +1,44 @@
+import os
+import sys
+
+from PyPDF2 import PdfReader, PdfWriter
+import requests
+import PyPDF2
+
+
+def add_watermark_to_pdf(pdf_file_in, pdf_file_mark, pdf_file_out):
+    """把水印添加到pdf中"""
+
+    input_stream = open(pdf_file_in, 'rb')
+    pdf_input = PdfReader(input_stream, strict=False)
+
+    # 获取PDF文件的页数
+    # pageNum = pdf_input.getNumPages()
+    pageNum = len(pdf_input.pages)
+
+    pdf_output = PdfWriter()
+    # 读入水印pdf文件
+    pdf_watermark = PdfReader(open(pdf_file_mark, 'rb'), strict=False)
+    # 给每一页打水印
+    for i in range(pageNum):
+        # page = pdf_input.getPage(i)
+        page = pdf_input.pages[i]
+        page.merge_page(pdf_watermark.pages[0])
+        page.compress_content_streams()  # 压缩内容
+        pdf_output.add_page(page)
+    pdf_output.write(open(pdf_file_out, 'wb'))
+
+
+
+if __name__ == '__main__':
+   
+    if len(sys.argv) < 3:
+        print("Usage: add_watermark.py <url> <pdf_file_in>  <pdf_file_out>  <pdf_file_mark>")
+        sys.exit(1)
+
+    pdf_file_url = sys.argv[1]
+    pdf_file_in = sys.argv[2] # 要加水印的文件名
+    pdf_file_out = sys.argv[3] # 加好水印的结果文件
+    pdf_file_mark = sys.argv[4]
+    add_watermark(pdf_file_url, pdf_file_in, pdf_file_mark, pdf_file_out)
+    print(pdf_file_out)

+ 103 - 0
app.py

@@ -0,0 +1,103 @@
+import os
+import sys
+import uuid
+
+from flask import Flask, request, jsonify
+
+from add_signature import find_signature_positions, add_signature_to_pdf
+from add_watermark import add_watermark_to_pdf
+from lib import Qiniu
+
+app = Flask(__name__)
+
+
+@app.route('/add_signature', methods=['POST'])
+def add_signature():
+    try:
+        # 获取请求参数
+        data = request.get_json()
+        pdf_url = data.get('pdf_url')
+        if not pdf_url:
+            return jsonify({"error": "缺少pdf_url参数"}), 400
+
+        # 下载PDF文件
+        local_pdf = Qiniu.download_file(pdf_url)
+
+        # 查找签名位置(使用默认公章图片)
+        signature_img = './static/报告专用章.png'  # 确保项目目录下有这个文件
+        positions = find_signature_positions(local_pdf)
+        if not positions:
+            os.remove(local_pdf)
+            return jsonify({"error": "未找到签名位置"}), 400
+
+        # 生成带公章的PDF
+        signature_pdf = os.path.join("./temp", f"signature_{os.path.basename(local_pdf)}")
+        add_signature_to_pdf(local_pdf, signature_pdf, signature_img, positions)
+
+        # 上传到七牛云
+        file_key = f"UpImage/{uuid.uuid4()}.pdf"
+        new_pdf_url = Qiniu.upload_to_qiniu(signature_pdf, file_key)
+
+        # 清理临时文件
+        os.remove(local_pdf)
+        os.remove(signature_pdf)
+
+        return jsonify({
+            "success": True,
+            "signature_pdf_url": new_pdf_url
+        })
+
+    except Exception as e:
+        return jsonify({
+            "error": str(e),
+            "success": False,
+        }), 500
+
+
+@app.route('/add_watermark', methods=['POST'])
+def add_watermark():
+    try:
+        # 获取请求参数
+        data = request.get_json()
+        pdf_url = data.get('pdf_url')
+        if not pdf_url:
+            return jsonify({"error": "缺少pdf_url参数"}), 400
+
+        # 下载PDF文件
+        local_pdf = Qiniu.download_file(pdf_url)
+
+        # 水印文件
+        pdf_file_mark = './static/watermark.pdf'  # 确保项目目录下有这个文件
+
+        # 生成带公章的PDF
+        watermark_pdf = os.path.join("./temp", f"watermark_{os.path.basename(local_pdf)}")
+        add_watermark_to_pdf(local_pdf, pdf_file_mark, watermark_pdf)
+
+        # 上传到七牛云
+        file_key = f"UpImage/{uuid.uuid4()}.pdf"
+        new_pdf_url = Qiniu.upload_to_qiniu(watermark_pdf, file_key)
+
+        # 清理临时文件
+        os.remove(local_pdf)
+        os.remove(watermark_pdf)
+
+        return jsonify({
+            "success": True,
+            "watermark_pdf_url": new_pdf_url
+        })
+
+    except Exception as e:
+        return jsonify({
+            "error": str(e),
+            "success": False,
+        }), 500
+
+
+if __name__ == '__main__':
+    print("项目地址:", os.path.dirname(__file__))
+    if len(sys.argv) != 2:
+        print("请填写端口号")
+        sys.exit()
+    app.debug = True  # 设置调试模式,生产模式的时候要关掉debug
+    # app.config['JSON_AS_ASCII'] = False
+    app.run(host='0.0.0.0', port=6500, debug=True)

+ 34 - 0
lib/Qiniu.py

@@ -0,0 +1,34 @@
+import os
+import tempfile
+import uuid
+
+import requests
+from qiniu import Auth, put_file, etag, BucketManager
+
+# 七牛云配置
+QINIU_ACCESS_KEY = '-8ezB_d-8-eUFTMvhOGbGzgeQRPeKQnaQ3DBcUxo'
+QINIU_SECRET_KEY = 'KFhkYxTAJ2ZPN3ZS3euTsfWk8-C92rKgkhAMkDRN'
+QINIU_BUCKET_NAME = 'bzdcoldverify'
+QINIU_BUCKET_DOMAIN = 'https://bzdcoldverifyoss.baozhida.cn'  # 例如:'http://xxx.clouddn.com'
+
+# 初始化七牛云Auth
+q = Auth(QINIU_ACCESS_KEY, QINIU_SECRET_KEY)
+
+def download_file(url):
+    """下载文件到临时目录"""
+    local_filename = os.path.join("./temp", str(uuid.uuid4()) + '.pdf')
+    with requests.get(url, stream=True) as r:
+        r.raise_for_status()
+        with open(local_filename, 'wb') as f:
+            for chunk in r.iter_content(chunk_size=8192):
+                f.write(chunk)
+    return local_filename
+
+def upload_to_qiniu(local_file, key):
+    """上传文件到七牛云"""
+    token = q.upload_token(QINIU_BUCKET_NAME, key)
+    ret, info = put_file(token, key, local_file)
+    if info.status_code == 200:
+        return f"{QINIU_BUCKET_DOMAIN}/{key}"
+    else:
+        raise Exception(f"七牛云上传失败: {info}")

+ 35 - 0
requirements.txt

@@ -0,0 +1,35 @@
+blinker==1.9.0
+certifi==2025.7.14
+cffi==1.17.1
+chardet==5.2.0
+charset-normalizer==3.4.2
+click==8.2.1
+cryptography==45.0.5
+docx==0.2.4
+et_xmlfile==2.0.0
+Flask==3.1.1
+idna==3.10
+itsdangerous==2.2.0
+Jinja2==3.1.6
+jpype1==1.6.0
+lxml==6.0.0
+MarkupSafe==3.0.2
+numpy==2.3.1
+openpyxl==3.1.5
+packaging==25.0
+pandas==2.3.1
+pdfminer.six==20211012
+pdfplumber==0.6.0
+pillow==11.3.0
+pycparser==2.22
+PyPDF2==3.0.1
+python-dateutil==2.9.0.post0
+pytz==2025.2
+qiniu==7.17.0
+reportlab==4.4.3
+requests==2.32.4
+six==1.17.0
+tzdata==2025.2
+urllib3==2.5.0
+Wand==0.6.13
+Werkzeug==3.1.3

BIN
static/watermark.pdf


BIN
static/报告专用章.png