# sendcard PHP 后端 提供一个接口用来向外部服务拉取 `token`,并把外部回传的 `data` 备档写入 SQLite 数据库。 ## 前置要求 - PHP 8.x(已使用 `declare(strict_types=1)`) - `curl` 扩展 - `PDO_SQLITE` 扩展 - SQLite 写入权限(默认写入到 `./data/sendcard.sqlite`) - 如果你用 Apache:需要启用 `.htaccess`(本项目已添加重写规则) ## 接口说明 ### 1) 获取 token `GET /api/getcard?type=xianyu` 请求参数: - `type`:必须等于 `xianyu`,否则会报错不执行外部请求 **说明(避免和轮询脚本混淆):** - **不需要**配置 `poll_cron_key`,也**不需要**在 URL 里传 `device_code` / `device_code_md5`。浏览器只访问带 `type=xianyu` 的地址即可。 - `device_code`、`device_code_md5`、`external_base_url`(例如 `82.157.20.83:9091/.../getCredentials?...`)都在服务端 **`config.php`** 里,由 **`getcard.php` 在服务器上** 用 cURL 去调外部接口;调用方看不到也不会传这些参数。此处对应 **机器 A**(即时 Web 请求)。 - 定时脚本 **`getcard_poll.php`** 仍调同一 `external_base_url`,但使用 **`config['poll']` 里机器 B** 的 `device_code` / `device_code_md5`,与机器 A 分离,便于在上游对 **单设备或请求频次** 有限制时分流(轮询与即时各走一套设备参数)。 成功响应(HTTP 200,**响应体为纯文本**,即 token 字符串本身,不是 JSON 包一层): ```text <外部返回的 data.token,例如 JWT 整段> ``` 错误响应: - 未带 `type=xianyu`: ```json {"error":"Invalid request parameter","need_type":"xianyu"} ``` - 外部接口返回异常/缺少数据: ```json {"error":"External response missing data"} ``` ## SQLite 备档 每次成功调用并拿到外部 `data` 后,会把外部返回的字段逐列拆开写入一行记录(并额外保存 `raw_json`)。 - 数据库文件:`./data/sendcard.sqlite` - 表名:`cursor_login_backups` 表字段包含(部分): - `external_msg`, `external_code` - `token`, `email`, `deviceCode`, `activationCode`, `status`, `createTime` 等 - `raw_json`:保存外部完整 JSON(可用于排查) ## 配置文件 `config.php` 里可配置: - `external_base_url`:`getcard` 与 `getcard_poll` **共用**的外部 `getCredentials` 基础地址 - `device_code`、`device_code_md5`:**机器 A**,仅 **`getcard.php`** 使用 - `poll.device_code`、`poll.device_code_md5`:**机器 B**,仅 **`getcard_poll.php`** 使用(与 A 不同,用于上游限流/限设备时分流) - `sqlite_path`:SQLite 路径 ## 部署路由(Apache / Nginx) 本项目包含 `.htaccess`(仅对 Apache 生效),用于把 `/api/getcard` 重写到 `api/getcard.php`。 如果你部署在宝塔(通常是 Nginx),建议直接访问: `/api/getcard.php?type=xianyu` 这样不依赖重写;如果你确实想保留 `/api/getcard` 形式,把你宝塔里“重写/伪静态”的设置截图或说明一下,我再给你对应的 Nginx 规则。 ## 接口说明 本仓库里的 config.php 只负责把现成的字符串传给外部接口,没有生成逻辑;下面按你这条 device_code 的常见写法说明各段含义。 device_code 的三段(用 - 拼起来) 示例: BFEBFBFF000A06A4 - 5210N57MP0004BXP00R2 - c84b8af03a6c40e888f72576a5f34a1f 段 典型来源(Windows 上常见叫法) 第 1 段 BFEBFBFF000A06A4 CPU ProcessorId(wmic cpu get ProcessorId / WMI Win32_Processor.ProcessorId)。BFEBFBFF 是 Intel 上很常见的 CPUID 特征前缀,后面一般是 family/model/stepping 等拼成的十六进制,不是传统意义上的“CPU 出厂序列号”,但常被叫作 CPU 标识/CPU ID。 第 2 段 5210N57MP0004BXP00R2 多为 主板序列号(Win32_BaseBoard.SerialNumber)或 BIOS/机器厂商序列 一类 OEM 串号;也可能是别的固定硬件标识,取决于采集工具具体取哪一项。 第 3 段 c84b8af…(32 位十六进制) 很像 去掉连字符的 UUID(128 bit)。常见对应是 Windows 机器 GUID(注册表 HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid 去掉 -),也可能是程序自己生成的 GUID,要看上游工具怎么定义。 所以你记的「CPU 序列 + 别的」大致对应:CPU ProcessorId + 主板/机器类序列号 + 机器 UUID(或同类唯一 ID),中间用 - 连成一条 device_code。 device_code_md5 从形态看是 32 位小写十六进制,符合 MD5 的常见输出。 在本项目里没有对 device_code 做校验或计算;我用 PHP 对整条 device_code 做 md5(),结果不等于你配置里的 bc5613415689b537679ba22c2feae68c。 因此它可能是:对别的规范化字符串(大小写、是否含 -、段顺序等)做的 MD5,或 带盐/由别的客户端算法 生成,需要以提供 device_code 的那套工具/文档为准。 若你需要和上游完全一致,建议对照当时生成 device_code 的程序或接口文档;若你手头有那套工具的源码或说明,发一段我可以帮你对齐 device_code_md5 的精确算法。 ## 定时轮询 getcard_poll.php 与 **`getcard.php` 调同一套外部接口、同一种返回数据**;差别在于:**`getcard` 是即时(Web)**,**`getcard_poll` 约每 5 分钟(计划任务 + 脚本内随机间隔)**。因上游常对 **单设备或请求频次** 有限制,**即时走机器 A**(顶层 `device_code` / `device_code_md5`),**轮询走机器 B**(`config['poll']` 内同名键,值与 A 不同)。 - **`getcard.php`**:带 `type=xianyu`,**不要 `poll_cron_key`**,用机器 A 调外部并写 SQLite、回 token。 - **`getcard_poll.php`**:命令行跑定时 **不要 `key`**;仅当用 HTTP 触发该脚本时配置 **`poll_cron_key`** 与 `?key=`(见下文方式二)。用机器 B 调外部,并写 **txt 日志**(与 getcard 的 SQLite 备档可并存)。 已在项目里提供 `api/getcard_poll.php`:宝塔 **计划任务每分钟** 执行一次;脚本内未到点会 `Skip`;到点后按 **约 5 分钟 ±3 分钟、不少于 4 分钟** 再请求外部,结果追加到 `data/getcard_poll_log.txt`。**请务必将 `poll` 里机器 B 的 `device_code` / `device_code_md5` 填全**,否则轮询脚本会因配置校验失败退出。 ### 宝塔里怎么设「定时」(二选一) **方式一:Shell 里直接跑 PHP(不经过 Web)** 1. 打开 **计划任务** → **Shell 脚本**(或同类「执行脚本」)。 2. 执行周期选 **每 1 分钟**(要足够密;未到点脚本会 `Skip` 退出,不会打外部接口)。 3. 脚本内容把路径换成你站点实际路径,例如: ```bash /usr/bin/php /www/wwwroot/你的域名/sendcard.yunzer.cn/api/getcard_poll.php >>/www/wwwroot/你的域名/sendcard.yunzer.cn/data/getcard_poll_cron.log 2>&1 ``` - 若不确定 PHP 路径:宝塔 **软件商店** → 已安装的 **PHP** → **设置** → **命令行版本**,常见为 `/www/server/php/82/bin/php`(版本号按你的来)。 **方式二:调你自己站点上的 URL(走 HTTP,等价于「调接口」)** 可以,不必在 Shell 里写 `php` 路径。先在 `config.php` 里配置 **`poll_cron_key`**(一长串随机密钥,勿泄露),则允许通过 Web 访问: `https://你的域名/api/getcard_poll.php?key=你配置的密钥` 然后任选其一: - 宝塔计划任务选 **访问 URL**(若有),每分钟访问上述地址;或 - Shell 计划任务里每分钟执行:`curl -fsS 'https://你的域名/api/getcard_poll.php?key=你的密钥' >>/path/to/curl.log 2>&1` **注意:** `key` 会出现在 **Web 服务器访问日志** 里,且可能被中间代理记录;更稳妥仍是 **方式一 CLI**。若用 Nginx 且未配置重写,请用带 `.php` 的路径:`/api/getcard_poll.php?key=...`。 ### 写入的文件 - `data/getcard_poll_state.json`:下次允许执行的时间(含文件锁,避免并发重复跑) - `data/getcard_poll_log.txt`:每次拉取的一行日志(时间 + HTTP 状态 + 整段 JSON;失败则写 `ERROR ...`) ### 间隔规则(脚本内已实现) - 本次执行后,下次间隔秒数:`max(240, 300 + random_int(-180, 180))`,即 **4~8 分钟** 之间随机。 若你希望轮询成功时也像 `getcard.php` 一样写入 SQLite,可以再说一下,我可以把那段插入逻辑复用到 `getcard_poll.php` 里。