feat: 顺运宝登录接入验证码自动识别 (#47)
#46 的登录只有手工输验证码一条路,而会话 24 小时就过期——每天第一次 同步都得有人在场,将来也做不了定时同步。 docs/admin/08 §8 当时写死"不引入 OCR 服务",理由是"多一个必须先启动的 东西"。那条判断基于示例脚本里的 http://127.0.0.1:8000/ocr(本机服务)。 用户提供了托管地址后前提不成立,本工单推翻它——文档里改写并保留原文, 让后来人知道这个决定变过、为什么变。 OCR 优先、手工兜底:识别成功直接登录,失败或服务不可达降级到 #46 已有的 手工弹窗,并在弹窗里说明是"已尝试 N 次"还是"服务不可用"。手工路径不删, 外部服务挂了不该让整个同步功能不可用。 识别失败也是 code:200。实测拿无文字图片探测 https://ocr.ilapage.cn/ocr 返回 {"code":200,"message":"Success","data":""}——不是错误码。所以 Recognize 只负责"这次 HTTP 调用有没有问题",空 data 照常返回 (", nil), 业务校验交给调用方;空 data 和长度不对收敛到同一个 len(code) != 4, 一条规则覆盖两种情况。 不合格的验证码不拿去登录:白费一次尝试,且频繁错误登录可能触发风控。 审查时变异测试发现这条没有测试守着——原测试只断言"重新取图了"和 "最终登录成功",禁用长度校验后依然成立。已补 loginRecorder 记录每次 提交到 /am/auth/login 的 code,断言登录只被调用一次且提交的是合格的那个。 每次重试重新取图(同一张图再识别结果一样,且可能已被上次失败的登录作废); OCR 用独立 HTTP 客户端不带顺运宝 Cookie;验证码图片只在内存里传,不落盘。 OCR 不可达立即降级、不占用重试次数——对着连不上的地址重试 5 次, 操作员要等 50 秒才看到手工输入框,结果注定一样。 测试全部用 httptest,不打真实的 ocr.ilapage.cn 和 shunyunbaoerp.com。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,8 +112,23 @@ password: 0012345 # ✗ 解析成整数 12345
|
||||
没有这个文件时,点「同步」会提示"没有找到配置文件……请复制
|
||||
config.example.yaml",不是一句读不出原因的报错。
|
||||
|
||||
`config.yaml` 里还有两个和验证码自动识别相关的配置(工单 #47):
|
||||
|
||||
```yaml
|
||||
syb:
|
||||
ocr_url: https://ocr.ilapage.cn/ocr # 留空则只用手工输入弹窗,不报错
|
||||
ocr_max_attempts: 5 # 识别失败的重试次数上限
|
||||
```
|
||||
|
||||
配了 `ocr_url` 后,会话过期时点「同步」会先自动识别验证码登录,
|
||||
无需人在场;识别失败或服务连不上会自动降级到手工输入弹窗,弹窗里
|
||||
会说明降级原因。`ocr_url` 留空就和 #46 时一样,一直走手工输入。
|
||||
|
||||
`[必须]` 验证码图片会被发送到 `ocr_url` 配置的地址,见
|
||||
[08 顺运宝接口](08-顺运宝接口.md) §8.1。
|
||||
|
||||
接口细节和这几个配置项各自的含义见
|
||||
[08 顺运宝接口](08-顺运宝接口.md) §8。
|
||||
[08 顺运宝接口](08-顺运宝接口.md) §8、§8.1。
|
||||
|
||||
## 4. 你应该看到什么
|
||||
|
||||
|
||||
+84
-4
@@ -379,10 +379,21 @@ Go 侧不用跟着调。
|
||||
`data/` 在 exe 旁边。示例脚本用 Redis 是因为它是反复启动的一次性脚本,
|
||||
进程间要传会话;Admin 是常驻进程,没有这个需求,持久化只为重启后免登录。
|
||||
|
||||
`[必须]` **不引入 OCR 服务。** 会话 24 小时,一天登录一次。
|
||||
为省一次手工输验证码而依赖 `127.0.0.1:8000` 不划算——多一个必须先启动的东西,
|
||||
而且 OCR 会失败(示例脚本自己写了 5 次重试),失败了照样要人工。
|
||||
界面上显示验证码图片、操作员输一次即可。
|
||||
`[决定已变更]` ~~不引入 OCR 服务。~~ 这条判断在工单 #47 里被推翻了,
|
||||
原文和推翻理由都留在这里,方便后来人知道这个决定变过、为什么变:
|
||||
|
||||
> 原判断(工单 #46):会话 24 小时,一天登录一次。为省一次手工输验证码
|
||||
> 而依赖 `127.0.0.1:8000` 不划算——多一个必须先启动的东西,而且 OCR
|
||||
> 会失败(示例脚本自己写了 5 次重试),失败了照样要人工。界面上显示
|
||||
> 验证码图片、操作员输一次即可。
|
||||
|
||||
`[必须]` **这条判断的前提是"本机服务 `127.0.0.1:8000`",托管服务不适用。**
|
||||
工单 #47 里用户提供了托管地址 `https://ocr.ilapage.cn/ocr`:没有要启动的
|
||||
东西,就是一次 HTTP 调用,"多一个必须先启动的东西"这条理由不成立了。
|
||||
而"每天第一次同步都要人在场"这个代价是实打实的——会话 24 小时过期,
|
||||
意味着做不了无人值守的定时同步。于是工单 #47 引入了 OCR 自动识别,
|
||||
失败或服务不可达时**降级**到原有的手工输入弹窗(那条兜底路径没有变),
|
||||
不是"失败了照样要人工"变成了"失败了才要人工"。详见下面「验证码自动识别」一节。
|
||||
|
||||
`[必须]` **凭据放 `admin/config.yaml`**,已在 `.gitignore` 里。
|
||||
仓库里提供 `admin/config.example.yaml` 作为模板(不含真实凭据)。
|
||||
@@ -402,6 +413,75 @@ password: "0012345" # ✓
|
||||
|
||||
---
|
||||
|
||||
## 8.1 验证码自动识别(工单 #47)
|
||||
|
||||
### 响应格式(已实测)
|
||||
|
||||
```
|
||||
POST https://ocr.ilapage.cn/ocr
|
||||
Content-Type: multipart/form-data,字段名 file
|
||||
→ 200 {"code":200,"message":"Success","data":"kycv"}
|
||||
```
|
||||
|
||||
实测耗时约 1.4 秒。
|
||||
|
||||
`[必须]` **识别失败也是 `code:200`,这是最容易写错的地方。**
|
||||
实测用一张无文字的图片探测,返回的是 `{"code":200,"message":"Success","data":""}`——
|
||||
不是错误码,是 `code:200` 加空 `data`。判断这一次调用真正"识别出了点什么",
|
||||
必须同时满足:HTTP 200、`code == 200`、**`data` 非空**。只看 `code` 会把
|
||||
"没识别出来"当成功,拿空字符串去登录。
|
||||
|
||||
`[必须]` 顺运宝验证码固定 **4 位字母数字**(08 §3.2 实测)。识别结果过滤
|
||||
空格标点后长度不是 4,说明识别错了,**不要拿去登录**——直接换一张图重试。
|
||||
拿明知不对的验证码去登录白费一次尝试,而且频繁的错误登录可能触发对方风控。
|
||||
|
||||
### 重试与降级
|
||||
|
||||
```text
|
||||
点同步 → 会话过期
|
||||
├─ ocr_url 已配置
|
||||
│ └─ 循环 ocr_max_attempts 次:取新验证码图 → OCR 识别 →
|
||||
│ 校验(非空 && 4位字母数字) → 登录
|
||||
│ ├─ 登录成功 ─────────────────→ 直接同步,无人值守
|
||||
│ └─ 次数用完仍失败 ────────────┐
|
||||
└─ ocr_url 未配置 / 请求本身失败 ────────┴─→ 弹手工输入框(#46 已有的兜底路径)
|
||||
```
|
||||
|
||||
`[必须]` **OCR 不可达要降级,不是报错。** 外部服务挂了不该让整个同步功能
|
||||
不可用——手工路径一直在,走它就是了。
|
||||
|
||||
`[必须]` 每次重试都要**重新取一张验证码图**。同一张图再识别一次结果一样,
|
||||
纯属浪费;而且验证码可能已经被上一次失败的登录作废。
|
||||
|
||||
`[必须]` OCR 请求本身失败(连不上、超时、返回非法 JSON、`code != 200`)判定为
|
||||
"服务不可用",**立即降级,不占用重试次数**——重试对"服务本身连不上"这种情况
|
||||
没有意义。只有"HTTP 调用成功但识别结果不合格(空/长度不对)"才占用一次重试。
|
||||
|
||||
`[必须]` 调 OCR **不带顺运宝的 Cookie**,用独立的 `http.Client`(独立的
|
||||
Cookie Jar、独立的超时),避免把顺运宝会话泄漏给另一个服务;也不共用
|
||||
顺运宝请求的超时,OCR 慢不该拖垮整个登录流程(`[建议]` 10 秒)。
|
||||
|
||||
`[必须]` **验证码图片全程在内存里传字节,不写文件。** 参考实现
|
||||
`raw_data/shunyunbaoerp_single.py` 把图片存成 `captcha.jpg` 是命令行脚本
|
||||
的做法,Admin 是常驻进程,写文件只会在 `data/` 里堆垃圾。
|
||||
|
||||
`[必须]` **验证码图片会被发送到 `ocr_url` 配置的外部服务。**
|
||||
当前 `https://ocr.ilapage.cn/ocr` 是用户自己的服务,不算交给第三方;
|
||||
把 `ocr_url` 换成别人运营的服务前,必须重新评估这一点。
|
||||
|
||||
### 配置
|
||||
|
||||
```yaml
|
||||
syb:
|
||||
# 验证码自动识别服务地址。留空则只用手工输入弹窗,不报错。
|
||||
ocr_url: https://ocr.ilapage.cn/ocr
|
||||
ocr_max_attempts: 5
|
||||
```
|
||||
|
||||
`[必须]` `ocr_url` 留空 = 禁用,直接走手工输入弹窗,**不报错**。
|
||||
|
||||
---
|
||||
|
||||
## 9. 已知未验证的部分
|
||||
|
||||
实现前应逐条确认,都只有单一样本支撑:
|
||||
|
||||
Reference in New Issue
Block a user