跨平台多用户环境下PDF表单“序列号生成的服务器端方案“

发布于:2025-05-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

在PDF表单处理中,经常需要为每个表单生成唯一的序列号或表单编号。当所有表单都在同一台计算机上由同一用户处理时,可以通过JavaScript将编号存储在另一个表单或全局JavaScript数据中来实现。然而,当需要在多台计算机或多个用户环境中使用时,就需要更复杂的服务器端解决方案。

解决方案架构

要实现跨多环境的序列号生成功能,我们需要:

  1. 运行在表单中的JavaScript程序
  2. 运行在服务器上的实际Web服务

这里的"服务器"概念很广泛,可以是Windows Server、Mac OS Server、Linux等专业服务器系统,也可以是安装了服务器软件(如免费的XAMPP)的普通工作站。

技术限制

需要注意的是,Acrobat JavaScript实现的SOAP调用功能在免费Adobe Reader中仅在文档应用了"Forms"权限时才可用。要实现"Forms"权限,需要LiveCycle Reader Extensions软件。

表单设计

我们需要创建一个至少包含两个字段的表单:

  • 一个字段用于存储表单编号/序列号
  • 一个字段用于填写表单创建者或接收者的姓名

此外,表单还需要一个按钮来请求新的序列号。生成新序列号时,系统还应存储用户名和当前时间日期,以便后续追踪文档处理情况。

Web服务实现

为了简化PDF表单中的实现,我们首先创建提供唯一编号的Web服务。该服务应提供一个名为"getSerialNumber()"的函数,该函数接受一个参数(用户名)并返回包含新编号的字符串。

数据库设计

我们使用MySQL数据库来生成唯一且连续的编号。通过在MySQL数据库中定义一个作为主键的字段,每次插入新记录时,该索引会自动递增(从第一条记录的1开始)。

以下是创建所需数据库和表的MySQL命令:

CREATE DATABASE serialnumbers;
CREATE TABLE serialnumbers (idx INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(30), date TIMESTAMP);
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT INSERT,SELECT ON serialnumbers to 'theUser'@'localhost';

PHP实现

以下是实现该系统的PHP脚本,它将用户名和当前日期写入数据库,然后返回新记录的索引:

<?php
    function getNextSerial($userName) {
        $mysqli = new mysqli("localhost", "theUser", "thePassword", "serialnumbers");
        
        if (mysqli_connect_errno()) {
            printf("Connect failed: %s\n", mysqli_connect_error());
            exit();
        }

        $query = "INSERT INTO serialnumbers (username, date) VALUES (\"" . $mysqli->real_escape_string($userName) . "\", NOW())";
        $mysqli->query($query);
        $idx = $mysqli->insert_id;
        $mysqli->close();
        return $idx;
    }
    
    ini_set("soap.wsdl_cache_enabled", "0");

    class getSerialResponse{
        public $return;
    }

    class getSerialClass{
        public function getSerialNumber($parameters){
            $response = new getSerialResponse();
            $response->return = getNextSerial($parameters->userName);
            return $response;
        }
    }   

    $server = new SoapServer("GetSerialNumber.wsdl");
    $server->setClass("getSerialClass");
    $server->handle();
?>

此脚本假设在同一目录中存在名为"GetSerialNumber.wsdl"的WSDL文件,该文件又引用也需要位于同一目录中的模式文件。

PDF表单中的JavaScript实现

在示例文档中,我们使用以下JavaScript代码:

// 获取WSDL代理对象
var myProxy = Net.SOAP.connect("http://localhost/GetSerial/GetSerialNumber.wsdl?wsdl");

// 从字段获取用户名
var userName = this.getField("UserName").value;
if (userName != "") {
    var result = myProxy.getSerialNumber(userName);
    this.getField("Result").value = util.printf("%04d", Number(result));
    // 隐藏按钮
    event.target.display = display.hidden;
}
else {
    app.alert("Please fill in a user name");
}

代码首先加载与PHP Web服务一起安装的WSDL文件,然后获取"UserName"字段的内容。如果该字段非空,则通过SOAP代理对象请求新的序列号,将结果格式化为带前导零的数字,并隐藏用于生成序列号的按钮。

部署注意事项

使用这些文件时,必须确保调整JavaScript和PHP代码中使用的所有URL,使其与您的安装匹配。示例中使用的是"http://localhost/GetSerial",您需要搜索该字符串并将其替换为安装目录的正确路径。

替代方案

如果SOAP实现过于复杂,可以考虑基于FDF的解决方案,它也能实现数据库与PDF表单之间的数据交互,而无需使用SOAP。

系统架构图

PDF表单 SOAP服务 MySQL数据库 调用getSerialNumber(用户名) INSERT记录(用户名,时间) 返回自增ID 返回格式化序列号 PDF表单 SOAP服务 MySQL数据库

修正后的PHP Web服务代码

<?php
// 错误报告设置(开发环境)
error_reporting(E_ALL);
ini_set('display_errors', 1);

/**
 * 获取下一个序列号
 * @param string $userName 用户名(自动过滤SQL注入)
 * @return int 序列号
 * @throws Exception 数据库错误时抛出
 */
function getNextSerial($userName) {
    // 数据库配置(应存储在配置文件中)
    $config = [
        'host' => 'localhost',
        'user' => 'serial_user',
        'pass' => 'secure_password_123',
        'db'   => 'serial_numbers_db',
        'port' => 3306
    ];

    // 创建安全的数据库连接
    $mysqli = new mysqli(
        $config['host'],
        $config['user'],
        $config['pass'],
        $config['db'],
        $config['port']
    );

    // 检查连接
    if ($mysqli->connect_errno) {
        throw new Exception("数据库连接失败: " . $mysqli->connect_error);
    }

    // 准备预处理语句防止SQL注入
    $stmt = $mysqli->prepare("INSERT INTO serial_numbers (username, create_date) VALUES (?, NOW())");
    if (!$stmt) {
        throw new Exception("预处理失败: " . $mysqli->error);
    }

    // 绑定参数并执行
    $stmt->bind_param("s", $userName);
    if (!$stmt->execute()) {
        throw new Exception("执行失败: " . $stmt->error);
    }

    $serial = $mysqli->insert_id;
    
    // 清理资源
    $stmt->close();
    $mysqli->close();
    
    return $serial;
}

// 禁用WSDL缓存(开发环境)
ini_set("soap.wsdl_cache_enabled", "0");

try {
    // 创建SOAP服务
    $server = new SoapServer("SerialService.wsdl");
    
    // 注册服务类
    $server->setClass("SerialService");
    
    // 处理请求
    $server->handle();
} catch (Exception $e) {
    // 记录错误日志
    file_put_contents('soap_error.log', date('[Y-m-d H:i:s] ') . $e->getMessage() . PHP_EOL, FILE_APPEND);
    
    // 返回SOAP错误
    header("HTTP/1.1 500 Internal Server Error");
    die($e->getMessage());
}

/**
 * SOAP服务类
 */
class SerialService {
    /**
     * 获取序列号
     * @param string $userName 用户名
     * @return string 格式化后的序列号(如"000123")
     */
    public function getSerialNumber($userName) {
        // 验证输入
        if (empty($userName) {
            throw new SoapFault("Client", "用户名不能为空");
        }
        
        if (strlen($userName) > 30) {
            throw new SoapFault("Client", "用户名最长30个字符");
        }

        try {
            $serial = getNextSerial($userName);
            return str_pad($serial, 6, '0', STR_PAD_LEFT);
        } catch (Exception $e) {
            throw new SoapFault("Server", $e->getMessage());
        }
    }
}
?>

修正后的JavaScript代码

/**
 * 生成序列号按钮点击事件
 */
function generateSerialNumber() {
    try {
        // 1. 获取用户名输入
        var userNameField = this.getField("UserName");
        var userName = userNameField.value.trim();
        
        // 2. 验证输入
        if (!userName) {
            app.alert({
                cTitle: "输入错误",
                cMsg: "请输入用户名后再生成序列号",
                nIcon: 2 // 警告图标
            });
            return;
        }
        
        // 3. 连接SOAP服务
        var wsdlUrl = "http://your-domain.com/SerialService.php?wsdl";
        var soapClient = Net.SOAP.connect(wsdlUrl);
        
        // 4. 调用服务
        var serialNumber = soapClient.getSerialNumber(userName);
        
        // 5. 显示结果
        this.getField("SerialNumber").value = serialNumber;
        this.getField("GenerateBtn").display = display.hidden;
        
        // 6. 记录生成时间
        this.getField("GenerationTime").value = util.printd("yyyy-mm-dd HH:MM:ss", new Date());
        
    } catch (e) {
        console.println("错误: " + e);
        app.alert({
            cTitle: "系统错误",
            cMsg: "生成序列号失败: " + e.message,
            nIcon: 3 // 错误图标
        });
    }
}

// 初始化时检查是否已有序列号
if (this.getField("SerialNumber").value) {
    this.getField("GenerateBtn").display = display.hidden;
}

数据库优化方案

-- 创建专用数据库
CREATE DATABASE serial_numbers_db 
DEFAULT CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;

-- 创建数据表(优化版)
CREATE TABLE serial_numbers (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(30) NOT NULL,
    create_date DATETIME NOT NULL,
    client_ip VARCHAR(45),
    user_agent VARCHAR(255),
    INDEX idx_username (username),
    INDEX idx_date (create_date)
) ENGINE=InnoDB;

-- 创建专用用户(最小权限原则)
CREATE USER 'serial_user'@'%' IDENTIFIED BY 'secure_password_123';
GRANT INSERT, SELECT ON serial_numbers_db.* TO 'serial_user'@'%';
FLUSH PRIVILEGES;

常见错误解决方案

错误类型 可能原因 解决方案
连接超时 服务器未启动/防火墙阻止 1. 检查PHP服务是否运行
2. 开放端口(通常80/443)
数据库错误 凭证不正确/表不存在 1. 验证数据库配置
2. 执行提供的SQL创建表
SOAP解析失败 WSDL文件错误 1. 确保WSDL可访问
2. 使用SoapUI测试服务
权限不足 Adobe Reader限制 1. 使用Acrobat Pro
2. 申请Reader扩展权限

部署检查清单

  1. 服务器环境

    • 安装PHP SOAP扩展 (php-soap)
    • 配置MySQL/MariaDB
    • 设置正确的文件权限
  2. PDF表单配置

    • 使用Acrobat Pro XI或更高版本
    • 启用JavaScript特权
    • 测试跨域访问
  3. 安全措施

    • 替换示例数据库密码
    • 配置HTTPS加密
    • 实施IP访问限制

如需进一步测试,可以使用以下SOAP请求示例(通过Postman):

POST /SerialService.php HTTP/1.1
Host: your-domain.com
Content-Type: text/xml;charset=UTF-8
SOAPAction: "urn:SerialService#getSerialNumber"

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="urn:SerialService">
   <soapenv:Header/>
   <soapenv:Body>
      <ser:getSerialNumber>
         <userName>测试用户</userName>
      </ser:getSerialNumber>
   </soapenv:Body>
</soapenv:Envelope>

应用场景示例:

以下代码实现了 PDF文档与远程SOAP服务交互 的功能,核心应用场景包括:

  1. 在PDF中动态获取网络时间(互联网授时)
  2. 通过SOAP协议调用远程Web服务
  3. 将服务返回结果嵌入PDF表单字段
  4. 实现动态文档内容更新(如序列号生成、时间戳)

适用于:电子合同签署时间认证、动态表单数据填充、许可证激活系统等场景。


// 获取网络时间服务
try {
    const cURL = "http://quan.suning.com/getSysTime.do";
    const oRequest = {
        "ns1:getSysTime": { 
            xmlns: "http://quan.suning.com/",
            inputString: "sysTime1"
        }
    };
    
    const response = Net.SOAP.request({
        cURL: cURL,
        oRequest: oRequest,
        cAction: "http://quan.suning.com/getSysTime",
        cVersion: SOAPVersion.version_1_2,
        bEncoded: true // 启用XML编码
    });
    
    app.alert("当前网络时间:" + response.sysTime2);
} catch (e) {
    console.println("SOAP请求错误: " + e.message);
    app.alert("时间同步失败,请检查网络连接");
}

使用
使用
发送请求
生成
调用服务
PDF_Application
+Net.HTTP
+Net.SOAP
+app.alert()
+util.printf()
SOAP_Client
+wireDump
+request()
+connect()
HTTP_Client
+request()
Time_Server
+getSysTime.do
+返回JSON/XML
WSDL_Proxy
+getSerialNumber()

  1. SOAP版本兼容性

    // 强制使用1.2版本
    cVersion: SOAPVersion.version_1_2
    
  2. 调试配置

    SOAP.wireDump = "true"; // 显示原始通信数据
    
  3. 跨域安全配置

    <!-- 服务器需配置CORS -->
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: POST
    
  4. PDF权限清单

    /* 需要启用的权限:
       - 网络访问
       - 表单提交
       - 动态内容执行
       - 外部对象访问
    */
    

常见错误解决方案
错误类型 解决方案
SOAPError: 检查XML命名空间与服务器端匹配
MethodNotAllowed 确保服务器允许POST方法
权限拒绝 启用Adobe Reader扩展权限
空响应 验证SOAP Action URI正确性
编码错误 设置bEncoded: true并检查XML结构

通过以上配置,可实现PDF文档与SOAP服务的稳定交互。建议使用Acrobat Pro XI以上版本进行调试,并始终在try-catch块中处理网络请求。


网站公告

今日签到

点亮在社区的每一天
去签到