# 海关179工具 **Repository Path**: luffyzhao-java/customs-179-tools ## Basic Information - **Project Name**: 海关179工具 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-06 - **Last Updated**: 2026-05-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 海关 179 加签上报 CLI 程序 > 基于 **Spring Boot 3** + **Picocli** 开发的海关 179 号公告**加签机**端命令行程序。 > 支持通过 **WebSocket 连接海关客户端签名服务**,实现与海关程序完全兼容的数字签名。 ## 目录 - [背景说明](#背景说明) - [业务流程](#业务流程) - [技术栈](#技术栈) - [快速开始](#快速开始) - [环境要求](#环境要求) - [编译打包](#编译打包) - [配置文件](#配置文件) - [运行方式](#运行方式) - [CLI 命令详解](#cli-命令详解) - [加签模式说明](#加签模式说明) - [Mock 模式(测试)](#mock-模式测试) - [WebSocket 模式(推荐生产)](#websocket-模式推荐生产) - [PKCS#11 模式(UKey/IC 卡)](#pkcs11-模式ukeyic-卡) - [Native 模式(本地控件)](#native-模式本地控件) - [平台接口约定](#平台接口约定) - [Windows 服务化部署建议](#windows-服务化部署建议) - [常见问题](#常见问题) - [注意事项](#注意事项) ## 背景说明 根据海关 179 号公告要求,跨境电商平台需要将订单支付数据加签后上报海关。由于加签过程需要插入**中国电子口岸操作员卡**,且海关客户端程序提供 WebSocket 签名服务,因此需要一个常驻于 Windows 电脑的**加签机程序**,通过轮询平台任务队列完成加签。 本项目参考海关官方对接文档及 [CSDN 技术文章](https://blog.csdn.net/u013753310/article/details/156298251) 的实现思路,基于 **Java / Spring Boot CLI** 开发,更便于与企业现有 Java 技术栈集成。 ## 业务流程 ``` ┌─────────┐ ① 请求(sessionID,orderNo) ┌─────────────┐ │ 海关 │ ───────────────────────────────> │ 平台服务器 │ └─────────┘ └──────┬──────┘ ^ │ │ ④ 平台转发加签后数据 │ ② 写入任务队列 │ │ │ ┌──────▼──────┐ └───────────────────────────────────────│ 加签机 │ ③ 轮询取任务 -> 加签 -> 上报 │ (本程序) │ └─────────────┘ ``` 1. **海关 -> 平台**:海关主动请求平台接口,携带 `sessionID`、`orderNo` 等参数。 2. **平台 -> 加签机**:平台将待加签数据写入任务队列。 3. **加签机 -> 平台**:本程序轮询获取任务,调用操作员卡进行**数字签名**,将结果上报。 4. **平台 -> 海关**:平台将加签后的完整报文转发给海关(**必须在 2 分钟内完成**)。 ## 技术栈 | 技术 | 版本 | 说明 | |------|------|------| | Java | 17+ | 运行环境 | | Spring Boot | 3.2.x | 应用框架 | | Picocli | 4.7.x | 命令行解析 | | Spring WebFlux | 3.2.x | HTTP 客户端(非阻塞) | | Java-WebSocket | 1.5.x | WebSocket 签名客户端 | | Lombok | 1.18.x | 代码简化 | | Maven | 3.8+ | 构建工具 | ## 快速开始 ### 环境要求 - JDK 17 或更高版本 - Maven 3.8+ - **Windows 操作系统**(海关驱动限制) - 可用的 USB 接口(插入操作员卡) - 安装海关客户端程序(提供 WebSocket 签名服务) ### 编译打包 ```bash # 克隆或下载代码后,在项目根目录执行 mvn clean package # 打包后自动复制到 bin/ 目录 cp target/customs179-sign-cli-1.0.0.jar bin/ cp src/main/resources/application.properties bin/ ``` ### 配置文件 编辑 `src/main/resources/application.properties`(或运行时外挂配置文件): ```properties # 平台服务器地址(你的业务服务端) customs179.platform-url=http://localhost:8080 # 轮询间隔(秒) customs179.poll-interval-seconds=10 # 获取任务接口 customs179.poll-endpoint=/api/sign/tasks # 上报结果接口 customs179.report-endpoint=/api/sign/report # 海关实时数据上传接口地址 # 测试环境: https://swapptest.singlewindow.cn/ceb2grab/grab/realTimeDataUpload # 生产环境: https://customs.chinaport.gov.cn/ceb2grab/grab/realTimeDataUpload customs179.customs-report-url=https://swapptest.singlewindow.cn/ceb2grab/grab/realTimeDataUpload # 加签模式: mock(模拟) / websocket(连接海关客户端) / pkcs11(PKCS#11) / native(本地控件) customs179.sign-mode=mock # 海关 WebSocket 签名服务地址(安装海关卡驱动后自动启动) customs179.ws-url=ws://127.0.0.1:61232 # 加签库路径(非 websocket 模式使用) customs179.sign-lib-path= # 证书别名/容器名 customs179.cert-alias= # 卡密码(强烈建议通过命令行 --pin 传入,不要写死在配置中) customs179.cert-pin= ``` **使用外部配置文件运行:** ```bash java -jar customs179-sign-cli-1.0.0.jar --spring.config.location=file:./application-prod.properties run ``` ### 运行方式 ```bash # 方式一:直接运行(默认启动轮询进程) java -jar customs179-sign-cli-1.0.0.jar # 方式二:使用启动脚本(已包含自带 JDK) bin/start.bat # Windows bin/start.ps1 # PowerShell bin/start.sh # Linux/Mac bin/start-daemon.sh # Linux 后台守护进程 # 方式三:指定关键参数运行 java -jar customs179-sign-cli-1.0.0.jar run \ --platform-url=http://192.168.1.100:8080 \ --interval=5 \ --sign-mode=websocket \ --pin=00000000 # 方式四:单次轮询(配合 Windows 计划任务 / Linux Cron) java -jar customs179-sign-cli-1.0.0.jar once # 方式五:测试加签(不连接平台,仅验证加签功能) java -jar customs179-sign-cli-1.0.0.jar test-sign "测试数据" # 查看帮助 java -jar customs179-sign-cli-1.0.0.jar --help ``` ## CLI 命令详解 ``` Usage: customs179 [-hV] [-p] [-a=] [-i=] [-l=] [-m=] [-u=] [--ws-url=] [COMMAND] 海关179公告加签上报 CLI 程序(加签机端) Options: -a, --alias= 证书别名/容器名 -h, --help Show this help message and exit. -i, --interval= 轮询间隔(秒) -l, --lib-path= 加签库路径(PKCS11 DLL 或本地控件) -m, --sign-mode= 加签模式: mock / websocket / pkcs11 / native -p, --pin IC卡/UKey/操作员卡密码(建议环境变量传入) -u, --platform-url= 平台服务器地址 -V, --version Print version information and exit. --ws-url= 海关 WebSocket 签名服务地址 Commands: once 单次轮询并退出(适合定时任务/Cron) run 启动轮询进程(默认命令) test-sign 测试加签功能(不连接平台) ``` ### `run` 命令选项 | 选项 | 说明 | 示例 | |------|------|------| | `-u, --platform-url` | 平台服务器地址 | `http://192.168.1.10:8080` | | `-i, --interval` | 轮询间隔秒数 | `5` | | `-m, --sign-mode` | 加签模式 | `mock` / `websocket` / `pkcs11` / `native` | | `--ws-url` | WebSocket 签名服务地址 | `ws://127.0.0.1:61232` | | `-l, --lib-path` | 加签库路径 | `C:\ep801\ep801.dll` | | `-a, --alias` | 证书别名 | `MyCert` | | `-p, --pin` | 密码(交互式输入,不回显) | `-p` 后提示输入 | ## 加签模式说明 ### Mock 模式(测试) - **用途**:本地开发、单元测试 - **原理**:对拼接后的原文做 SHA-256 哈希并 Base64 编码 - **限制**:无真实法律效力,不能用于生产环境上报海关 ### WebSocket 模式(推荐生产) - **用途**:通过 WebSocket 连接海关客户端程序的签名服务(推荐) - **原理**:安装海关卡驱动后,客户端程序自动启动 WebSocket 服务(默认 `ws://127.0.0.1:61232`)。本程序通过 WebSocket 发送 `cus-sec_SpcSignDataAsPEM` 请求,由海关程序调用操作员卡完成硬件加密签名 - **前置条件**: 1. 下载安装海关卡驱动:`https://update.singlewindow.cn/downloads/EportClientSetup_V1.6.56.exe` 2. 插入操作员卡 3. 确认海关客户端程序已启动(任务管理器中可见相关进程) - **请求格式**: ```json {"_id": "1", "_method": "cus-sec_SpcSignDataAsPEM", "args": {"inData": "待签名数据", "passwd": "00000000"}} ``` - **响应格式**: ```json {"_status": "00", "_args": {"Result": true, "Data": ["签名值", "证书号"]}} ``` ### PKCS#11 模式(UKey/IC 卡) - **用途**:使用中国电子口岸等标准 UKey/IC 卡 - **原理**:通过 Java `SunPKCS11` Provider 加载 `.dll` 驱动,访问 KeyStore 中的私钥进行签名 - **待完善**:本项目已预留 `signByPkcs11()` 方法,需根据实际驱动引入如下代码: ```java Provider p = Security.getProvider("SunPKCS11"); KeyStore ks = KeyStore.getInstance("PKCS11", p); ks.load(null, pin.toCharArray()); PrivateKey pk = (PrivateKey) ks.getKey(alias, pin.toCharArray()); Signature sig = Signature.getInstance("SM3withSM2"); sig.initSign(pk); sig.update(signText.getBytes(StandardCharsets.UTF_8)); byte[] signed = sig.sign(); return Base64.getEncoder().encodeToString(signed); ``` ### Native 模式(本地控件) - **用途**:海关提供专用 ActiveX/DLL 控件(如 ep801、ep900 等) - **原理**:通过 **JNA** 或 **JNI** 调用本地 DLL 暴露的签名函数 - **待完善**:本项目已预留 `signByNative()` 方法,需根据控件的 C 头文件定义接口映射 ```java public interface SignDll extends Library { SignDll INSTANCE = Native.load("SignAPI", SignDll.class); int SignData(byte[] inData, int inLen, byte[] outData, int[] outLen); } ``` ## 平台接口约定 加签机与平台之间需实现以下两个 HTTP 接口: ### 1. 获取待加签任务 ```http GET /api/sign/tasks Accept: application/json ``` **成功响应(code=10000):** ```json { "code": "10000", "message": "success", "data": [ { "taskId": "T202405060001", "sessionID": "a1b2c3d4e5f6", "orderNo": "ORDER202405060001", "serviceTime": 1714972800000, "payExchangeInfoHead": { "guid": "a1b2c3d4e5f6", "initalRequest": "原始请求...", "initalResponse": "原始响应...", "ebpCode": "电商平台代码", "payCode": "支付企业代码", "payTransactionId": "交易流水号", "totalAmount": 2000.00, "currency": "502", "verDept": "3", "payType": "1", "tradingTime": "20240506103000", "note": "备注" }, "payExchangeInfoLists": [ { "orderNo": "ORDER202405060001", "goodsInfo": [ {"gname": "商品名称", "itemLink": "http://..."} ], "recpAccount": "6217000010000000001", "recpCode": "收款企业编码", "recpName": "收款企业名称" } ], "createdAt": 1714972800000 } ] } ``` ### 2. 上报加签结果 ```http POST /api/sign/report Content-Type: application/json ``` **请求体:** ```json { "taskId": "T202405060001", "sessionID": "a1b2c3d4e5f6", "orderNo": "ORDER202405060001", "success": true, "signValue": "Base64EncodedDigitalSignature...", "certNo": "37012345678901", "signTime": "2024-05-06 10:30:00", "signedMessage": "{完整加签后的JSON报文}" } ``` ### 加签原文格式 根据海关 179 号公告接口文档,加签原文字符串格式为: ``` sessionID||payExchangeInfoHead(JSON)||payExchangeInfoLists(JSON)||serviceTime ``` 例如: ``` fe2374-8fnejf97-32839218||{"guid":"...","ebpCode":"..."}||[{"orderNo":"..."}]||1533271903898 ``` ### 海关直报接口(可选) 如需直接上报海关,接口地址: - **测试环境**:`https://swapptest.singlewindow.cn/ceb2grab/grab/realTimeDataUpload` - **生产环境**:`https://customs.chinaport.gov.cn/ceb2grab/grab/realTimeDataUpload` 返回码规范: - `10000` = 成功 - `20000` = 失败 - `20001~21000` = 异常信息 ## Windows 服务化部署建议 为了让加签机随 Windows 开机自启且后台运行,建议使用以下方式: ### 方案一:NSSM 封装为 Windows Service ```powershell # 1. 下载 nssm (https://nssm.cc/) # 2. 注册服务 nssm install Customs179SignCLI # Path: C:\Program Files\Java\jdk-21\bin\java.exe # Arguments: -jar "D:\customs179\bin\customs179-sign-cli-1.0.0.jar" run --sign-mode=websocket --pin=00000000 # Working directory: D:\customs179\bin # 3. 启动服务 nssm start Customs179SignCLI ``` ### 方案二:Windows 计划任务(`once` 模式) 1. 打开 **任务计划程序** 2. 创建基本任务 -> 每天/每隔 1 分钟重复 3. 操作:启动程序 `java.exe` 4. 参数:`-jar "D:\customs179\bin\customs179-sign-cli-1.0.0.jar" once` ## 常见问题 **Q: 程序启动后提示 "轮询任务失败"?** A: 检查 `platform-url` 和 `poll-endpoint` 是否配置正确,确保平台服务端接口已启动且网络可达。 **Q: WebSocket 模式下提示 "连接失败"?** A: 确认已安装海关卡驱动并插入操作员卡,检查任务管理器中是否有海关客户端进程运行,确认 `ws-url` 配置正确(默认 `ws://127.0.0.1:61232`)。 **Q: PKCS#11 模式下报错 "找不到库"?** A: 确认 `sign-lib-path` 指向正确的 `.dll` 文件路径(如 `C:\Windows\System32\epso2016.dll`),并确保 JDK 位数与 DLL 位数一致。 **Q: 签名后海关返回 "签名验证失败"?** A: 检查加签原文拼接规则是否与海关文档要求完全一致。字段顺序、空值处理、编码格式都可能导致验签失败。正确格式为:`sessionID||payExchangeInfoHead(JSON)||payExchangeInfoLists(JSON)||serviceTime`。 **Q: 如何在同一台机器运行多个加签机(多卡)?** A: 准备多份配置文件,分别指定不同的 `cert-alias`,使用不同端口或标识区分,分别启动: ```bash java -jar customs179-sign-cli.jar --spring.config.location=file:./card1.properties run java -jar customs179-sign-cli.jar --spring.config.location=file:./card2.properties run ``` ## 注意事项 - ⏱ **时效性**:从海关发起请求到最终上报,整个流程**必须在 2 分钟内完成**。建议将轮询间隔设为 `5~10` 秒。 - 🔒 **密码安全**:`cert-pin` 强烈建议通过命令行 `--pin` 传入或使用环境变量,**切勿提交到代码仓库**。 - 🖥 **系统限制**:海关加签控件及 WebSocket 签名服务目前仅支持 **Windows 系统**。 - 🔌 **硬件维护**:确保操作员卡已正确插入且驱动正常,定期检查卡片有效期。 - 📝 **日志查看**:日志默认输出到控制台,生产环境建议在 `application.properties` 中配置 `logging.file.name` 写入文件。 --- 如有问题,请参考海关 179 号公告官方文档及接口规范。