报告发布 · 老师端(腾讯云 COS)
用途
把本地跑出来的 HTML 报告一键发布到腾讯云对象存储(COS),按日期归档、维护一个索引页(学员打开索引就能看到历史每天的报告),并直接给出可分享的访问链接。
配套技能:tdx-report-read(学员端,负责从 COS 读取报告)。
何时使用
- 报告生成完毕,需要上传到 COS 供他人查看。
- 需要每天发布一版,且历史版本不能被覆盖。
- 需要给学员/同事一个固定的入口页(索引),而不是每次发一个新链接。
前置条件
- Node.js 18+,已安装官方 SDK:
npm install cos-nodejs-sdk-v5
(若 SDK 装在别处,用NODE_PATH指向其node_modules目录) - 腾讯云 API 密钥(SecretId / SecretKey)+ 存储桶名 + 地域。
- 建议用子账号密钥并只授予写入权限,详见
references/cos_setup.md。
环境变量
| 变量 | 必填 | 说明 | 默认 |
|---|---|---|---|
TENCENT_SECRET_ID |
是 | 子账号密钥 ID(AKID 开头) |
— |
TENCENT_SECRET_KEY |
是 | 子账号密钥 Key | — |
COS_BUCKET |
是 | 存储桶名,含 APPID,如 xxx-1250000000 |
— |
COS_REGION |
否 | 地域 | ap-shanghai |
COS_PREFIX |
否 | 对象前缀 | stock-reports/<今天>/ |
COS_PUBLIC |
否 | 1 时对象显式设为公有读 |
跟随桶策略 |
> 密钥只在创建时可见一次,务必妥善保存;不用时建议在控制台「禁用」。
用法
# 1) 环境自检(SDK / 凭证 / 桶是否通)
node scripts/verify_setup.js
# 2) 发布当前目录下的 HTML 报告(按今天归档 + 更新索引页)
# 注意:存储桶非「公有读」时必须加 --public,否则学员端匿名读取会 AccessDenied。
node scripts/publish_cos.js --dir <报告目录> --public
# 可选参数
--dir <dir> 报告所在目录,默认当前目录
--prefix <p> 对象前缀,默认 stock-reports/<今天>/
--include-csv 连 CSV 一起传(默认只传 HTML)
--no-index 不更新索引页(只传文件)
--public 对象显式设为公有读
# 3) 推荐做法:把当期报告拷进一个干净目录再发布
# (脚本会扫描 --dir 下所有 .html 全量上传,不能让历史报告混进来)
node scripts/publish_cos.js --dir _publish --prefix stock-reports/2026-10-20/ --public
每日产物与学员端对应关系
| 本地文件 | 对象键 | 学员端用途 |
|---|---|---|
强势龙头组合12因子量化股票池_TOP50.html |
top50_pool_v7.html |
学员主看的总榜页(统计卡 + 因子进度条 + 🆕新进) |
强势龙头组合12因子量化股票池_TOP80.html |
top80_pool_v7.html |
完整池备选,可下钻到第 80 名 |
强势龙头组合12因子证据打分_v7.html |
12factor_report_v7.html |
全量证据报告(教师自用,通常不发布给学员) |
> 两张总榜页由引擎同一个模板 render_pool(n, rows) 渲染,TOP50 / TOP80 自动生成,只改标题与统计口径。
工作流
- 确认产物:在
--dir目录里找*.html(--include-csv时再加*.csv)。 - 自检:跑
scripts/verify_setup.js,确认 SDK 已装、凭证有效、桶可达。失败就按提示修,不要盲目重试。 - 上传:逐个
PutObject,Content-Type 按扩展名设置;HTML 报告设Content-Disposition: inline(中文原名写进filename*,仅作另存为建议名),浏览器点链接直接打开页面;CSV 才用attachment。对象同时显式设x-cos-acl: public-read。 - 更新索引:读取桶里的
stock-reports/index.json,写入/覆盖当天条目,再生成stock-reports/index.html(按日期倒序,列出每天的报告链接)。 - 收敛(关键,不可省略):运行
node scripts/converge_index_top50.js,把当天及所有历史日期的索引收敛为「仅 TOP50 一个文件」——它会删除云端多余的 v7 全量 / TOP80 对象,只保留学员要看的 TOP50。若跳过这步,学员端会看到 3 个文件卡片(v7/TOP50/TOP80)。脚本用环境变量PUBLISH_SCRIPT定位publish_cos.js的renderIndex()(未设时 fallback 到技能文件夹绝对路径);「今天」的条目禁止被自愈删除(只清历史孤儿),且用本地时区判日期。 - 回报:打印每个对象的大小与访问链接,并给出索引页地址。
v1.0.2 修复(学员端「点链接看不到」)
publish_cos.js对 HTML 报告的Content-Disposition由attachment改为inline:旧版学员点链接只会下载.html文件,手机/微信内置浏览器里经常打不开,表现成「报告看不到」。CSV 仍按attachment处理。converge_index_top50.js重写index.json/index.html时补x-cos-acl: public-read:与发布脚本--public同口径,避免收敛把索引改成私有、学员匿名读不到索引导致整份报告消失。- 学员端
fetch_report.js移除签名链接生成路径,对外一律匿名永久地址;references/read_setup.md旧口径文档整体重写。 - 升级后需对此前已上传的报告对象重传一次才会生效(对象元数据不会自动变更);之后每天自动发布即为新头。
v1.0.1 加固记录(与 daily_run.py --phase finalize 同口径)
publish_cos.js --public显式设置x-cos-acl: public-read,不再依赖桶级公有读配置。--prune-older的守卫正则修为/(^|\/)\d{4}-\d{2}-\d{2}\/$/:旧版用^\d{4}匹配,但因对象键前缀是stock-reports/,守卫永不命中、清理静默失效。- 本次有任一上传失败就跳过
--prune-older:保留昨天那份可用报告兜底,避免把云端唯一可访问的旧报告删掉。 - COS SDK 一律用 Promise 形式
await cos.xxx({...});callback 形式会挂起。 converge_index_top50.js与项目根版本保持 MD5 一致:自愈只删历史孤儿、结尾对被 delete 的日期判空(旧版会抛 TypeError 且对象已物理删除)。
关键约定
- 默认只传 HTML 报告,不传 CSV 与原始数据;需要时用
--include-csv。 - 日期目录按本地日期生成,不会覆盖历史报告。同一天重复发布是覆盖更新,属正常行为。
- 对象键 URL 必须按段
encodeURIComponent(整段编码会把/变成%2F,链接就废了)。 - 报告桶按公有读运行:索引页、报告对象、链接都对学员匿名可读,且报告为
inline在线打开。学员端对外链接固定为匿名永久地址,不输出签名 URL;若真把桶改成私有,学员端链接会直接 403,需另行设计带鉴权的分发方式,不能只靠学员端签名读取。
常见问题
| 现象 | 原因 / 处理 |
|---|---|
未找到 cos-nodejs-sdk-v5 |
未安装 SDK:npm install cos-nodejs-sdk-v5,或用 NODE_PATH 指过去 |
The specified bucket does not exist |
桶名或地域写错(地域错也会报这个) |
AccessDenied / 签名错误 |
密钥无该桶写入权限,检查子账号策略的 resource 是否指向该桶 |
| 链接打不开 | 桶/对象不是 public-read,用 --public 重新发布;收敛脚本 1.0.2 起也会保留公有读 |
点链接变成下载 .html,手机上看不到 |
该对象由旧版(≤1.0.1)上传,响应头是 Content-Disposition: attachment;升级后重新发布或重传该对象,变为 inline |