- Go 53.6%
- TypeScript 40.9%
- Shell 4.2%
- CSS 0.7%
- Makefile 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- 支付:接入 ePayGo/彩虹 v1(MD5)、v2(RSA)与 Mock 沙箱, 支持异步通知验签、金额校验、幂等激活与主动查单 - 计费:打印任务按页数 × 份数计费,黑白/彩色独立单价; PDF 解析真实页数,图片与文本使用估算规则 - 订单:待支付任务不进入设备队列,支付成功后自动入队; 支持取消、过期重付与未支付任务重试保护 - 套餐:管理员配置价格/时长/每日配额/优先级,购买后自动开通或续期, 到期惰性降级 - 前端:会员套餐、支付页、二维码、套餐管理、支付订单页面; 创建任务展示价格并跳转支付 - 文档:支付配置、商户接入、Mock 测试、计费规则与 API 说明 - 测试:v1/v2 签名验签、页数计价、订单幂等、套餐续期、取消与 Mock 端到端 |
||
| cloud | ||
| deploy | ||
| docs | ||
| local | ||
| scripts | ||
| .gitattributes | ||
| .gitignore | ||
| go.work | ||
| go.work.sum | ||
| Makefile | ||
| README.md | ||
RpiPrinter
面向树莓派与多平台的云端打印解决方案,由两个独立模块组成:
- 云端模块(
cloud/):Go + Gin + GORM + SQLite 后端,负责账号与角色、设备管理、按页计费、VIP 套餐购买、支付回调、任务下发、彩色能力协商、打印预览、代理 OTA 发布,并内嵌 React 管理控制台。 - 本地控制模块(
local/):Go 代理,可在树莓派/Linux、macOS(CUPS)与 Windows(系统打印队列)上运行;主动长轮询领取任务、调用打印驱动、上报打印机能力与任务进度,并支持崩溃自动重启与 OTA 自更新。
┌──────────────────────────────┐ HTTP/JSON 长轮询 ┌──────────────────────────┐
│ 云端模块 cloud/ │ <────────────────────────── │ 本地控制模块 local/ │
│ │ │ │
│ React 控制台 (Zustand/Router)│ ──────────────────────────> │ 注册 / 心跳 / 领取任务 │
│ Gin API ─ GORM ─ SQLite │ 任务文件 + 状态回报 │ CUPS / ESC-POS / RAW │
└──────────────────────────────┘ └────────────┬─────────────┘
│ lp / 设备写入
┌────▼─────┐
│ 打印机 │
└──────────┘
目录结构
RpiPrinter/
├── cloud/ 云端模块(独立 Go module)
│ ├── cmd/server/ 服务入口
│ ├── internal/
│ │ ├── api/ 用户/管理/设备/支付/OTA API
│ │ ├── auth/ JWT、bcrypt、密钥管理
│ │ ├── billing/ 页数统计、计价、订单激活与套餐权益
│ │ ├── payment/ 彩虹 v1/v2、RSA/MD5 验签、Mock 支付
│ │ ├── config/ 环境变量配置
│ │ ├── database/ SQLite 初始化与 AutoMigrate
│ │ ├── jobs/ 任务状态机、优先级领取
│ │ ├── model/ User / Plan / PaymentOrder / Device / Printer / PrintJob
│ │ └── storage/ 上传文件与代理发布包存储
│ └── web/ React 控制台
│ ├── src/components/ 布局、预览、任务对话框、shadcn 风格组件
│ ├── src/pages/ 登录注册 / 仪表盘 / 打印机 / 任务 / 会员 / 支付 / 用户 / 套餐 / 订单 / 发布 / 设置
│ ├── src/stores/ Zustand(主题、认证)
│ └── src/lib/ API 客户端、格式化、状态映射
├── local/ 本地代理(独立 Go module)
│ ├── cmd/agent/ 代理入口(跨平台信号处理)
│ ├── internal/
│ │ ├── agent/ 会话、心跳、任务处理、OTA 调度
│ │ ├── cloud/ 云端 HTTP 客户端
│ │ ├── config/ JSON 配置加载
│ │ ├── printer/ Driver 抽象 + CUPS / Windows / ESC/POS / RAW
│ │ └── update/ 校验、替换可执行文件、更新标记
│ └── config.example.json
├── scripts/
│ ├── deploy-local.sh 构建 + SSH 上传代理
│ ├── remote-install.sh 树莓派端安装/supervisor 启动
│ ├── run-agent.sh 轻量守护循环(OTA/崩溃重启)
│ ├── publish-agent.sh 多平台构建并发布 OTA
│ └── setup-mi-printer.sh 小米喷墨打印机 CUPS 配置
├── docs/api.md 设备端、用户端与管理端 API 文档
├── docs/payment.md ePayGo 支付、按页计费与 VIP 套餐说明
├── docs/printer-mi-inkjet.md 小米米家喷墨一体机兼容说明
├── deploy/systemd/ 树莓派 systemd 服务
├── deploy/windows/ WinSW 服务配置
├── deploy/macos/ launchd 服务配置
├── go.work 两个 Go module 的 workspace
└── Makefile
技术栈
| 模块 | 技术 |
|---|---|
| 云端后端 | Go 1.26、Gin、GORM、SQLite(glebarez/sqlite 纯 Go 驱动,无需 CGO)、JWT、bcrypt、log/slog |
| 云端前端 | React 18、TypeScript、Vite 7、Tailwind CSS v4、shadcn 风格组件、Zustand、React Router 7、qrcode.react |
| 支付 | ePayGo / 彩虹易支付 v1(MD5)、v2(RSA/SHA256)、Mock 沙箱;PDF 页数解析(dslipak/pdf) |
| 本地代理 | Go 1.26、HTTP 长轮询、CUPS(Linux/macOS)、Windows Spooler(PowerShell)、ESC/POS(GBK)、RAW |
| OTA | 云端版本清单 + SHA-256 校验 + 可执行文件替换,由 systemd/launchd/WinSW/supervisor 重启 |
快速开始
前置:Go 1.26+、Node.js 20.19+。Windows / macOS / Linux 均可开发和运行代理。
首次打开控制台需要注册账号:第一个注册用户会自动成为管理员。请先完成管理员注册,再对外开放服务。
支付功能默认关闭,打印任务免费且直接进入队列。需要收费时设置 APP_PAYMENT_ENABLED=true 并配置 ePayGo/彩虹商户信息,详见 docs/payment.md。开发环境可以使用 Mock 支付。
1. 启动云端服务
cd cloud
export APP_ENROLLMENT_KEY=dev-secret # PowerShell: $env:APP_ENROLLMENT_KEY="dev-secret"
go run ./cmd/server
服务默认监听 :8080,数据写入 cloud/data/(SQLite + 上传文件)。首次启动会自动建表。
2. 启动管理控制台
cd cloud/web
npm install
npm run dev
打开 http://localhost:5173。开发模式下 Vite 会把 /api 代理到 http://localhost:8080。
生产构建后由云端直接托管:
cd cloud/web && npm run build
cd .. && go run ./cmd/server # 存在 web/dist 时自动托管 SPA
3. 启动树莓派本地代理
cd local
cp config.example.json config.json
# 编辑 config.json:server_url、enrollment_key、device_name、printers
go run ./cmd/agent -config config.json
代理会以本地生成的 device_uid 注册设备,保存云端下发的 Token,然后开始心跳与长轮询。CUPS 打印机自动发现;ESC/POS 与 RAW 打印机在配置文件中声明。
在控制台“打印任务”页面选择打印机、上传文档即可下发。任务流转:
pending → claimed → printing → completed / failed
pending / claimed → canceled
failed / canceled → pending(控制台重试)
4. 一键构建并部署到树莓派
在 Git Bash / Linux / macOS 中执行:
bash scripts/deploy-local.sh
默认目标为 xiaoman1221@192.168.10.124,部署到远端 ~/rpiprinter/。脚本会:
- 交叉编译
linux/arm64与linux/arm(兼容 32/64 位 Raspberry Pi OS); - 打包二进制、示例配置和打印机配置脚本;
- 通过一条 SSH 连接上传并安装,只需在终端输入一次 SSH 密码;
- 远端已有
config.json时保留配置并重启代理;首次部署会生成配置但不启动,等待你填写server_url与enrollment_key。
常用选项:
bash scripts/deploy-local.sh --host xiaoman1221@192.168.10.124
bash scripts/deploy-local.sh --no-start # 只上传
bash scripts/deploy-local.sh --build-only # 只构建,产物在 .deploy-build/
RPI_PORT=2222 bash scripts/deploy-local.sh # 非标准 SSH 端口
make deploy-pi # 等价于执行部署脚本
部署完成后查看日志:
ssh xiaoman1221@192.168.10.124
tail -f ~/rpiprinter/agent.log
该脚本使用
nohup在用户目录运行代理,不依赖 sudo。若希望注册为系统服务,请参考下方 systemd 部署方式。
小米米家喷墨一体机兼容
目标设备 USB ID:302c:1511 Mi All-in-One Inkjet Printer。
该机型无官方 Linux 驱动,但支持 AirPrint,Linux 下首选 CUPS + ipp-usb + IPP Everywhere 无驱动方案。部署代理后,在树莓派执行:
sudo bash ~/rpiprinter/setup-mi-printer.sh
# 可选:提交测试页
sudo TEST_PAGE=1 bash ~/rpiprinter/setup-mi-printer.sh
脚本会安装 CUPS/ipp-usb、校验 USB 设备、自动创建名为 Mi_Inkjet 的驱动无关队列。若固件未暴露 IPP-over-USB,脚本会给出 Wi-Fi IPP 与兼容 PPD 的回退方案。
详细说明、验证命令与故障排查见 docs/printer-mi-inkjet.md。
配置
云端环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
APP_ADDR |
:8080 |
HTTP 监听地址 |
APP_DATA_DIR |
./data |
数据目录 |
APP_DB_PATH |
$APP_DATA_DIR/printer.db |
SQLite 文件路径 |
APP_UPLOAD_DIR |
$APP_DATA_DIR/uploads |
打印文档目录 |
APP_WEB_DIR |
./web/dist |
前端构建产物目录,存在时托管 SPA |
APP_CORS_ORIGINS |
http://localhost:5173,http://127.0.0.1:5173 |
允许的前端来源,逗号分隔 |
APP_ENROLLMENT_KEY |
空 | 设备注册密钥;为空时允许任意设备注册,仅限开发 |
APP_MAX_UPLOAD_MB |
50 |
单个上传文件大小上限 |
APP_MAX_POLL_WAIT_SEC |
30 |
设备长轮询最长挂起时间 |
APP_DEVICE_OFFLINE_SEC |
45 |
超过该时长无心跳视为离线 |
APP_HEARTBEAT_SEC |
15 |
下发给设备的心跳间隔建议值 |
APP_JWT_SECRET |
自动生成 | JWT 签名密钥(至少 32 字符);未设置时保存到 $APP_DATA_DIR/jwt.secret |
APP_TOKEN_TTL_HOURS |
24 |
登录令牌有效期 |
APP_REGISTRATION_ENABLED |
true |
是否开放注册;首个注册用户自动成为管理员 |
APP_USER_DAILY_QUOTA |
20 |
普通用户每日打印任务配额,0 表示不限 |
APP_VIP_DAILY_QUOTA |
100 |
VIP 每日打印任务配额,0 表示不限 |
APP_RELEASE_DIR |
$APP_DATA_DIR/releases |
代理 OTA 发布包目录 |
APP_MAX_AGENT_UPLOAD_MB |
100 |
单个代理发布包大小上限 |
APP_PAYMENT_ENABLED |
false |
支付总开关;关闭时打印任务免费 |
APP_PAYMENT_PROVIDER |
mock |
mock / rainbow_v1 / rainbow_v2 |
APP_PAYMENT_BASE_URL |
空 | ePayGo / 彩虹网关地址 |
APP_PAYMENT_PID / APP_PAYMENT_KEY |
空 | 彩虹商户 PID 与 v1 MD5 密钥 |
APP_PAYMENT_PRIVATE_KEY / _FILE |
空 | v2 商户 RSA 私钥 PEM(内容或文件) |
APP_PAYMENT_PLATFORM_PUBLIC_KEY / _FILE |
空 | v2 平台公钥 PEM |
APP_PUBLIC_URL |
空 | 公网 HTTPS 地址,用于回调与支付返回;启用支付时必填 |
APP_PAYMENT_DEFAULT_CHANNEL |
alipay |
默认支付渠道 |
APP_PAYMENT_CHANNELS |
alipay,wxpay |
前端可选渠道,逗号分隔 |
APP_PAYMENT_MOCK_ENABLED |
false |
Mock 模式下是否允许模拟支付 |
APP_PRICE_BW_FEN |
30 |
黑白每页单价(分) |
APP_PRICE_COLOR_FEN |
100 |
彩色每页单价(分) |
APP_PAYMENT_MAX_PAGES |
200 |
单任务最大计费页数 |
APP_PAYMENT_ORDER_TTL_MIN |
120 |
订单展示有效期 |
APP_PAYMENT_RECONCILE_SEC |
60 |
主动查单/对账间隔 |
APP_PAYMENT_NOTIFY_SKEW_SEC |
3600 |
v2 通知时间戳容忍范围 |
支付配置的完整说明与商户配置步骤见 docs/payment.md。
本地代理 config.json
| 字段 | 默认值 | 说明 |
|---|---|---|
server_url |
http://127.0.0.1:8080 |
云端地址 |
enrollment_key |
空 | 与云端 APP_ENROLLMENT_KEY 一致 |
device_name |
主机名 | 控制台显示名称 |
state_dir |
./data |
设备 UID / Token 持久化目录 |
spool_dir |
$state_dir/spool |
下载任务文件的临时目录 |
poll_wait_seconds |
25 |
长轮询等待秒数(云端最大 30) |
heartbeat_seconds |
15 |
心跳间隔,最小 5 |
worker_count |
1 |
并发打印 worker 数,最大 8 |
keep_spool |
false |
是否保留打印后的 spool 文件 |
auto_update |
true |
是否启用云端 OTA 自动更新 |
update_check_seconds |
21600 |
更新检查间隔,最小 30 秒 |
printers[] |
— | 非系统自动发现的打印机声明,见下 |
printers[] 字段:
| 字段 | 说明 |
|---|---|
name |
全局唯一打印机名,任务按此路由 |
display_name |
控制台显示名称 |
driver |
escpos 或 raw(Linux/macOS 的 CUPS 队列无需配置) |
port |
ESC/POS 或 RAW 直写设备,如 /dev/usb/lp1、COM3: |
queue |
RAW 模式下的 CUPS 队列名(以 -o raw 提交;Windows 不支持) |
is_default |
是否标记为默认打印机 |
常用环境变量可覆盖配置:RPI_SERVER_URL、RPI_ENROLLMENT_KEY、RPI_DEVICE_NAME、RPI_STATE_DIR、RPI_SPOOL_DIR。
账号、角色与配额
- 首个注册用户自动成为 管理员;后续注册用户默认
user。 - 角色:
user:创建/查看自己的任务,按每日配额限制。vip:更高每日配额;任务优先级为 100,会被设备优先领取。admin:不受配额限制,可管理用户、查看全部任务、发布代理版本。
- 管理员可在“用户管理”页面调整角色、启用/禁用账号、重置密码;必须保留至少一个启用管理员。
- 用户密码使用 bcrypt 存储;JWT 默认 24 小时有效,改密/重置密码/禁用会使旧令牌失效。
- 配额按“当日创建的任务数”计算,超限返回
429;控制台仪表盘会显示已用/剩余配额。
彩色打印与预览
- 代理在上报打印机时附带
supports_color与capabilities:- CUPS 通过
lpoptions -p <queue> -l探测print-color-mode/ColorModel/ColorMode; - Windows 通过
Get-PrintConfiguration获取 Color; - ESC/POS 与 RAW 直写固定为仅黑白。
- CUPS 通过
- 创建任务时用户可选择“打印机默认 / 黑白 / 彩色”;后端会拒绝在不支持彩色的打印机上选择彩色。
- CUPS 驱动会把颜色模式转换为对应的
-o参数。 - 创建任务时可预览本地 PDF/图片/文本;历史任务可点击“预览”通过鉴权接口拉取文件后预览。上传目录不会静态暴露。
支付与 VIP 套餐
- 支付网关:ePayGo,支持彩虹 v1(MD5)、v2(RSA/SHA256)与本地 Mock 沙箱;默认关闭。
- 打印任务按 页数 × 份数 收费,黑白/彩色分别定价。PDF 使用真实页数,图片按 1 页,文本按每 50 行一页估算。
- 付费任务创建后状态为
pending_payment,支付成功后才进入pending,设备在此之前无法领取。 - 用户可在“会员套餐”页面购买后台配置的套餐;支付成功后自动开通/续期 VIP,到期后自动恢复普通用户。
- VIP 享受更高每日配额与任务优先级,但打印仍需付费;管理员免支付、免配额。
- 管理员可在“套餐管理”配置价格、时长、配额与优先级,在“支付订单”查看订单并主动查单。
- 未支付任务占用配额,取消后释放;同一用户最多保留 5 个待支付任务。
完整流程、环境变量、商户配置与 Mock 测试见 docs/payment.md。
打印驱动
- CUPS(Linux/macOS):通过
lpstat -p/lpstat -o自动发现队列并查询状态,通过lp提交文件。支持任务options中的 CUPS 选项(如media、sides)。 - Windows Spooler:通过 PowerShell
Get-Printer/Get-PrintConfiguration枚举打印机与彩色能力;文本使用Out-Printer,PDF/图片使用Start-Process -Verb PrintTo。成功仅代表已提交到 Windows 打印队列,彩色/份数控制为尽力而为。 - ESC/POS:向配置的
port写入指令流。文本文件转为 GBK 并发送初始化、中文模式、走纸与切纸指令;.prn/.bin原样发送。可用任务选项cut(默认true)和feed(默认3)控制。 - RAW:通过 CUPS 队列
-o raw或直接写设备文件,原样发送字节,适合标签机等自带指令格式的设备。
CUPS 的
lp只保证“已进入队列”,代理会在提交成功后立即回报completed。如需精确的物理打印完成状态,后续可增加lpstat -W completed跟踪或 IPP 事件监听。
树莓派部署
交叉编译 64 位 ARM 二进制:
make agent-arm64 # 输出 bin/rpi-printer-agent
安装为 systemd 服务(按实际用户名修改 unit 中的 User):
sudo install -d /opt/rpiprinter /etc/rpiprinter /var/lib/rpiprinter
sudo install -m 0755 bin/rpi-printer-agent /opt/rpiprinter/
sudo install -m 0644 local/config.example.json /etc/rpiprinter/config.json
# 编辑 /etc/rpiprinter/config.json:
# "state_dir": "/var/lib/rpiprinter",
# "spool_dir": "/var/lib/rpiprinter/spool"
sudo install -m 0644 deploy/systemd/rpi-printer-agent.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now rpi-printer-agent
journalctl -u rpi-printer-agent -f
OTA 自更新要求
/opt/rpiprinter对运行用户可写。若 service 以非 root 用户运行,请先sudo chown -R <user>:<group> /opt/rpiprinter。systemd unit 已设置Restart=always,更新退出后会自动拉起新版本。
树莓派需先安装并配置 CUPS:
sudo apt install cups cups-client
使用 make deploy-pi 或 bash scripts/deploy-local.sh 部署时,会使用 run-agent.sh 作为轻量 supervisor,同样支持崩溃重启和 OTA。
多平台运行
一次性构建所有目标:
make agent-all
产物位于 bin/:
rpi-printer-agent-linux-arm64
rpi-printer-agent-linux-arm
rpi-printer-agent-linux-amd64
rpi-printer-agent-darwin-arm64
rpi-printer-agent-darwin-amd64
rpi-printer-agent-windows-amd64.exe
rpi-printer-agent-windows-arm64.exe
Windows
- 将对应
.exe、config.json放到同一目录(不要放在Program Files,自更新需要写权限)。配置模板见deploy/windows/config.example.json。 - 前台测试:
.\rpi-printer-agent-windows-amd64.exe -config .\config.json - 安装为服务:参考
deploy/windows/rpi-printer-agent.xml,使用 WinSW 注册服务。WinSW 的onfailure会在 OTA 退出后自动拉起新版本。 - 打印机通过 Windows Spooler 自动发现;文本、PDF/图片的打印能力依赖本机已安装的打印处理器与文件关联。
macOS
- 将
rpi-printer-agent-darwin-arm64(或 amd64)安装到/usr/local/rpiprinter/,放入config.json。 - 前台测试:
./rpi-printer-agent -config ./config.json - 使用
deploy/macos/com.rpiprinter.agent.plist注册 launchd 服务:launchctl load -w ~/Library/LaunchAgents/com.rpiprinter.agent.plist - macOS 使用内置 CUPS,打印机可通过
lpstat -p自动发现。
macOS 未签名/未公证的 OTA 二进制可能被 Gatekeeper 拦截;生产发布建议进行签名与公证。
代理 OTA 自动更新
管理端“代理发布”页面可以按平台/架构上传代理二进制并发布;也可以使用脚本一次性构建并发布:
VERSION=v0.3.0 NOTES="支持彩色打印" bash scripts/publish-agent.sh
代理行为:
- 每
update_check_seconds(默认 6 小时)向云端查询当前 OS/Arch 的最新已发布版本; - 下载到可执行文件所在目录,校验大小与 SHA-256;
- 等待当前任务和长轮询结束,阻止新任务领取;
- 备份当前二进制为
.old,原子替换,然后以专用退出码75退出; - supervisor / systemd / launchd / WinSW 拉起新版本;新版本首次启动会清理更新标记与
.old。
要求:可执行文件目录对运行用户可写,并且有服务管理器负责重启。回滚时可手动将 rpi-printer-agent.old 改回 rpi-printer-agent。
API 文档
设备端与管理端接口、请求/响应示例见 docs/api.md。
常用命令
make tidy # 整理两个 Go module 依赖
make run-cloud # 启动云端
make web-dev # 启动前端开发服务器
make web-build # 构建前端
make agent # 本机运行代理(读取 local/config.json)
make agent-arm64 # 交叉编译树莓派 64 位代理
make agent-all # 构建 Linux/macOS/Windows 全部代理
make deploy-pi # 构建并上传到树莓派(交互输入 SSH 密码)
make publish-agent # 构建并发布 OTA 版本(需管理员密码)
make test # 运行 Go 测试
当前边界 / 后续工作
- Windows 打印依赖本机文件关联与打印处理器,
PrintTo只能保证“已提交到 Spooler”,份数与彩色控制无法完全保证。 - macOS 未签名/未公证的 OTA 包可能被 Gatekeeper 拦截。
- ESC/POS 状态目前只能判断设备文件是否存在,未实现双向状态查询(如 GS r 状态回读)。
- 设备离线期间的任务不会重新调度;已领取但未完成的任务需要人工在控制台重试。
- 上传文件在任务完结后不会自动清理,需要后续增加保留策略。
- 设备心跳/任务上报尚无本地 outbox,云端不可达时最终状态可能丢失(会重试 3 次)。
- 首个注册用户成为管理员适合单实例部署;公网开放前请先完成管理员注册。
- JWT 存储在浏览器 localStorage,需通过 HTTPS、CSP 与输入转义降低 XSS 风险。
- 支付不提供自动退款;支付成功但打印失败时需管理员人工处理。
- 迟到支付不会复活已取消的打印任务,订单会标记为需要人工复核。
- 文本打印页数为估算值,实际打印机分页可能不同。
- 真实支付需要 ePayGo/彩虹商户凭据、公网 HTTPS 回调地址;本地开发使用 Mock 支付。
- 后续可加入 WebSocket 推送、多租户、审计日志与更细粒度的权限组。