PowerBI数据分析静态HTML看板发布到阿里云 OSS 并绑定自有域名:完整操作指引

静态看板发布到阿里云 OSS

并绑定自有域名 · 完整操作指引

从”本地 HTML 看板”到”客户点开即看的 HTTPS 短链”

含工具脚本设计全过程、控制台截图与踩坑实录

项目 内容
适用场景 把本地做好的静态 HTML 看板(含 ECharts 等)发布到云端,用自有域名给客户一个短的、点开即看的 HTTPS 链接
后端 阿里云 OSS(国内正式交付);脚本同时兼容 Cloudflare R2(公开样板 / 备份)
实战环境 bucket:accunion-dashboard(华东1 杭州)|域名:kanban.accunion.cn|看板:BOM 成本差异分析
读者 需要复现这套流程的同事 / AI Agent(无需编程经验,照第 5 章命令执行即可;想改脚本看第 4 章)
成文日期 2026-09-29
配套文件 工具/看板发布/ 下的 publish.py、build_single.py、secrets_file.py、init.py、密钥.txt、使用说明.md

阅读顺序建议:第 2 章先建立整体认知 → 第 5 章照着跑通 → 跑通后再回头看第 4 章(脚本设计)和第 7 章(踩坑)。如果只想快速交付,只看第 5 章即可。

第 1 章 这份文档能帮你做什么

1.1 读完你能得到什么

本文完整记录了一次真实交付:把一个 136 KB 的静态 HTML 看板,发布到阿里云对象存储,绑定公司自有二级域名,配上 HTTPS 证书,最终给客户一个形如 https://kanban.accunion.cn/c003/bom 的短链接——客户点开就能看,不用下载、不用登录、地址栏干净。

文档中每一步都附了当时的控制台截图、实际执行的命令、真实输出和踩过的坑。照着做一遍,大约 1~2 小时可以跑通(其中大部分时间在等证书签发)。

1.2 前置条件清单

动手前请先确认以下各项都具备,缺一项都会卡住:

# 需要什么 说明 / 怎么确认
1 一个已备案的域名 国内 OSS 绑定自定义域名要求 ICP 备案。本例用 accunion.cn 的二级域名 kanban.accunion.cn。没有备案见 7.2 的替代方案
2 阿里云账号 + 实名认证 能进 OSS 控制台即可
3 本机能跑 Python 3.12 本文命令里的 Python 路径需替换成你本机的;依赖 oss2 / boto3 装在这个解释器上
4 做好的静态看板目录 目录里有 index.html(引用 styles.css / app.js / data.js 等本地文件)
5 (可选)Node.js 仅当需要重新生成本文档时才用;发布流程本身不需要

1.3 名词表(先把这几个词对齐)

名词 含义
OSS / Bucket 阿里云对象存储,以及其中的”桶”。桶是顶层容器,本例只有一个桶 accunion-dashboard,靠路径前缀区分不同客户
Object / Key 桶里的一个个文件。Key 就是它的路径,如 c003/bom/index.html
前缀 prefix Key 的前半段。本文规定写成两段”客户码/看板码”,如 c003/bom,它直接就是链接的后半段
私有 / 公共读 私有=必须带签名才能访问;公共读=任何人拿到链接都能看。含真实财务数据一律私有
签名 URL 给私有对象生成的临时访问链接,带 OSSAccessKeyId / Expires / Signature 三个参数,到期失效
单文件 把 CSS/JS/数据全部内联进一个 index.html。私有交付必须这样做,否则只签了首页、css/js 全是 403
CNAME 把自定义域名指向 OSS 的域名解析记录
短链 / 跳转壳 一个公开的极小 HTML,用 iframe 加载私有的真实看板,让客户地址栏保持短链
RAM 用户 阿里云的子账号。用它生成 AccessKey,比用主账号 AK 安全得多

第 2 章 先想清楚:为什么这么设计

2.1 目标与约束

目标很朴素:让客户在微信/邮件里点一个链接,立刻看到看板。但有三条硬约束:

数据不能公开。看板里是客户真实的成本、供应商、单价,一旦设成公共读,链接被转发就等于泄露。

客户不能折腾。不能让客户下载文件、解压、装环境,也不能让链接长得吓人。

客户会越来越多。一家客户可能有好几个看板,结构必须能横向扩展,不能每家客户建一个桶。

2.2 四种交付方案的取舍

当时对比过四条路,最终选了第 4 条:

方案 做法 问题 结论
① 直接发文件 把 HTML 打包微信发给客户 客户要下载、双击打开;更新一次发一次;手机上打不开 ❌ 体验差
② 公开直链 对象设公共读,直接给 URL 数据裸奔;且 OSS 默认域名会强制下载(见 7.1) ❌ 不安全
③ 私有 + 签名长链接 私有对象 + 7 天签名 URL 安全,但链接一长串(含签名参数、中文还会被编码),且 7 天就得重发一次 ⚠ 可用但难看
④ 私有 + 固定短链 真实文件私有,另加一个公开”跳转壳”指向它 链接固定不变、地址栏干净;真实数据仍受签名保护 ✅ 最终采用

为什么不用”每家客户一个桶”?—— 桶多了权限要配多遍、AK 要授多个、运维成本高。一个桶靠前缀分层(c002/…、c003/…)就够,权限策略也只需写一份、限定这一个桶。

2.3 最终架构(这张表是全文核心)

一个看板在云端其实是 3 个对象,理解了这个,后面所有操作都顺理成章:

云端对象 权限 从哪来 作用
c003/bom/index.html 私有(签名访问) 本地 看板/dist/index.html 真实看板,含全部数据
c003/bom 公共读 脚本自动生成 跳转壳:iframe 加载真实页,客户访问入口
c003 公共读 脚本自动生成 客户总入口:列出该客户所有看板

对应到链接:

单个看板 → https://kanban.accunion.cn/c003/bom
客户总入口 → https://kanban.accunion.cn/c003

2.4 三个”必须”(这三条是硬道理)

必须打成单文件。私有桶里每个对象都要单独签名,而 index.html 里是相对引用,签了首页也加载不到 css/js/data,页面必白屏。

必须绑自定义域名。OSS 默认域名会强制下载(响应头 x-oss-force-download),客户点了只会下载一个 html 文件。两条绕过办法实测均无效(见 7.1)。

证书必须”部署”,不只是”签发”。证书控制台显示”已签发”不等于生效,必须再点一次”部署到云资源”,否则 HTTPS 报证书不匹配(见 7.3)。这是本次最隐蔽的坑。

第 3 章 阿里云侧准备(控制台操作,带截图)

3.1 创建 RAM 用户并授权(不要动用主账号 AK)

在 RAM 控制台创建用户时会看到下面这个提示。请务必选”RAM 用户 AccessKey”,不要图省事用云账号 AccessKey——后者一旦泄露等于整个阿里云账号失守。

图 1 控制台的 AccessKey 选型提示(红框为当时标注的两条路)

为什么不用 STS Token:STS 临时凭证确实更安全(不用轮换),但要定时刷新 token,对”一个月发一两次看板”的场景太重。RAM 静态 AK + 最小权限策略已经足够。

创建用户(访问方式勾”OpenAPI 调用访问”):

图 2 创建 RAM 用户,勾选 OpenAPI 调用访问

AccessKey Secret 只在创建成功的那一瞬间显示一次,之后再也查不到。当场复制保存,否则只能删掉重建。

然后给这个用户挂一条最小权限策略(只准操作这一个桶,连建桶权限都不给):

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "oss:GetBucketInfo", "oss:ListObjects",
        "oss:PutObject",  "oss:GetObject",
        "oss:DeleteObject", "oss:PutObjectAcl"
      ],
      "Resource": [
        "acs:oss:*:*:accunion-dashboard",
        "acs:oss:*:*:accunion-dashboard/*"
      ]
    }
  ]
}

把上面两处 accunion-dashboard 换成你的桶名即可直接粘贴。完整文件见 工具/看板发布/ram-policy-example.json。

3.2 创建 Bucket

OSS 控制台 → 创建 Bucket。本例的关键设置如下:

图 3 创建 Bucket:名称、地域、存储类型

图 4 创建 Bucket:版本控制、读写权限等

设置项 本例取值 为什么
名称 accunion-dashboard 全局唯一,建议公司/项目维度命名,不要按客户命名(一个桶装所有客户)
地域 华东1(杭州) 决定 endpoint:oss-cn-hangzhou.aliyuncs.com,必须和密钥文件里的 endpoint 一致
读写权限 私有 含真实财务数据,绝不开公共读
存储类型 低频访问 看板访问频次低,更便宜;有 30 天最短存储期,看板体量下可忽略
版本控制 不开 每次发布都是整文件覆盖,不需要历史版本
阻止公共访问 保持关闭 ⚠ 短链跳转壳依赖”单对象可设公共读”。此项若开启,短链会失效(详见 7.6)

建好后可以在”基础信息”里核对一遍:

图 5 Bucket 创建完成,核对地域、读写权限与存储类型

3.3 绑定自定义域名

路径:OSS 控制台 → 选中桶 → 传输管理 → 域名管理 → 绑定域名。

填入你要用的域名,本例是 kanban.accunion.cn。

阿里云会提示去 DNS 加一条 CNAME 记录,按提示值添加即可(本例解析到了 118.31.219.204)。

等解析生效(通常几分钟,最长 24 小时)。可以用 ping 或 nslookup 验证。

验证解析是否生效:

nslookup kanban.accunion.cn
# 或
python -c "import socket; print(socket.gethostbyname('kanban.accunion.cn'))"

3.4 HTTPS 证书:申请 → 签发 → 部署(三步,缺一不可)

在”域名管理”里点证书配置,可以申请阿里云免费 DV 证书(每个域名 20 分钟~数小时签发)。签发完成后,证书列表里会显示”已签发”:

图 6 证书已签发:状态”已签发”,有效期 2026-09-29 ~ 2026-12-28

⚠ 最大的坑来了:看到”已签发”就以为好了 —— 不对。此时访问 https 仍会报证书不匹配(Hostname mismatch),因为 OSS 还在用自己的默认证书。必须再做一步”部署到云资源”。

部署路径(两条都行):

① 数字证书管理服务控制台 → 选中该证书 → 部署 → 云产品部署 → 选 对象存储 OSS / 目标 bucket / 目标域名

② OSS 控制台 → 桶 → 传输管理 → 域名管理 → 点击域名 → 修改证书配置 → 选中这张证书

部署完成后,在证书的”云产品部署记录”里应能看到一条 OSS 记录:

图 7 云产品部署记录:已部署到 OSS / accunion-dashboard / kanban.accunion.cn

这一步目前没有 SDK 接口(oss2 无证书部署 API),只能控制台点。别指望脚本自动化。

3.5 权限排错口诀

第一次跑脚本报权限错时,按这张表判断,能省很多时间:

报错 真正原因 怎么办
InvalidAccessKeyId 密钥本身错了(ID 抄错 / 用错用户的) 重新核对 AccessKeyId
AccessDenied 密钥对,但这个 RAM 用户没被授权 去 RAM 控制台给该用户挂 3.1 的策略
The bucket you access does not belong to you 也是没授权!OSS 不向无权限者透露桶是否存在 同上,别误判成桶名写错
oss:PutBucket 相关 策略里没给建桶权限 手动建桶即可(推荐),或给策略加 oss:PutBucket

第 4 章 工具脚本是怎么设计的(给 AI / 想改脚本的人)

4.1 为什么要自己写脚本

OSS 有控制台、有图形化工具,但都不适合这件事:

发布是高频重复动作(每月续期、改数据后重发),点几次鼠标必然出错;

真正麻烦的部分——强制 MIME、Cache-Control、单文件内联、签名后换域名、生成短链壳——控制台根本做不了;

要做到”一次对接,以后一条命令”,就必须脚本化。

最终目标形态:所有配置写在一个文本文件里,以后每次只跑一条命令,传完直接吐出给客户的链接。

4.2 文件清单与各自职责

文件 职责 备注
密钥.txt 唯一配置源:AK、桶、域名、发布方式、看板登记 明文,已 gitignore,勿外传
secrets_file.py 解析密钥.txt 的纯文本格式(键=值、# 注释、键名不敏感) 被 init.py 和 publish.py 共用
init.py 一次性对接向导:测连通、测读写、测签名、写配置 有密钥文件时免交互;支持 –yes 无人值守
publish.py 发布主程序:打包 → 上传 → 签名 → 生成短链/导航页 核心文件,日常只用这一个
build_single.py 把本地 css/js/图片全部内联成一个自包含 HTML 私有交付必须
使用说明.md 完整说明文档 命令速查、安全合规、常见报错
ram-policy-example.json RAM 最小权限策略模板 改 2 处桶名即可粘贴
gen_docx.js 本文档的生成脚本 重新生成:node 工具/看板发布/gen_docx.js

4.3 配置设计:让”密钥.txt”成为唯一配置源

设计原则是:改配置只改这一个文件,不用重新跑 init,也不用碰代码。配置优先级:

环境变量 > 密钥.txt > publish_config.json

这样换机器时用环境变量覆盖,本地改文件即可。密钥.txt 支持这些键(部分):

键 作用 本例取值
backend oss 或 r2 oss
oss.endpoint 地域对应的 endpoint https://oss-cn-hangzhou.aliyuncs.com
oss.accessKeyId / accessKeySecret RAM 用户的 AK (敏感,不在此写出)
oss.bucket 桶名 accunion-dashboard
oss.publicHost 自定义域名(可带 http:// 前缀降级) kanban.accunion.cn
public / sign / single 是否公开、签名时效、是否打包单文件 false / 7d / true
short / shortSign / jump 是否生成短链、短链背后签名时效、是否直接跳转 true / 30d / false
board.<客户>.<看板>.dir 本地看板目录 客户档案/003、…/看板
board.<客户>.<看板>.prefix 云端前缀=链接后半段 c003/bom

键名规范化的坑:解析器会把键名按段规范化(下划线/连字符转驼峰),所以新加驼峰字段(如 shortSign)必须同时登记到 CANON 映射表,否则会被压成全小写而匹配不上。这是实际踩过的(见 7.9)。

4.4 登记设计:客户 / 看板 两级

为了支持”一个客户多个看板”,登记名用点号分层:

board.小园香径.BOM成本差异.dir    = 客户档案/003、小园香径独徘徊(个人客户)/2、BOM成本差异分析/看板
board.小园香径.BOM成本差异.prefix = c003/bom

于是命令也支持两级:

publish.py --client 小园香径              # 发该客户全部看板 + 自动生成总入口
publish.py --client 小园香径.BOM成本差异   # 只发其中一个
publish.py --list                        # 查看已登记了哪些

prefix 必须写成两段”客户码/看板码”,且用纯英文数字。因为它就是链接的后半段,写中文会被浏览器编码成 %E5%B0%8F%E5%9B%AD… 一长串,正是这次要消灭的问题。

4.5 build_single.py:为什么必须内联,以及怎么改通用

私有桶下每个对象都要单独签名,而 index.html 里写的是 这种相对引用——签名只签了首页,浏览器再去请求 css/js 时没有签名,全部 403,页面白屏。解决办法只剩一个:把所有东西塞进一个 HTML。

第一版写死了三个文件名(styles.css / app.js / data.js),结果第二个看板翻车了——它还有一个 143 KB 的地图数据 pakistan.js 没被内联,客户看到的图缺地图。改成”扫描并内联所有本地引用”:

# 不写死文件名:把 index.html 里所有本地 css / js / 图片都内联
def repl_script(m):
    u = m.group("u")
    p = board_dir / u
    if not _is_local(u) or not p.exists():
        return m.group(0)
    return "<script>\n" + _safe_js(p.read_text(encoding="utf-8")) + "\n</script>"

html = LINK_RE.sub(repl_link, html)    # <link href="x.css"> → <style>…</style>
html = SCRIPT_RE.sub(repl_script, html)  # <script src="x.js"> → <script>…</script>
html = IMG_RE.sub(repl_img, html)      # <img src="x.png">  → data:image/png;base64,…

两个细节:① 内联进 会提前截断标签,要转义;② 图片转 base64。改完后第二个看板单文件从 2487.9 KB 变成 2631.0 KB,正好多了那 143 KB。

4.6 publish.py:主流程与两个关键点

主流程:读配置 → 找看板目录 → 打包单文件 → 上传到

/index.html → 生成签名 URL → 生成短链壳 → 生成客户导航页 → 打印链接。

关键点一:上传时必须强制 MIME 和缓存头,否则 OSS 默认给 application/octet-stream,浏览器会拒绝加载 CSS/JS:

self.bucket.put_object_from_file(
    key, str(path),
    headers={
        "Content-Type": "text/html; charset=utf-8",   # 必须带 charset
        "Cache-Control": "no-cache, max-age=0",       # 改完 data.js 客户刷新即生效
    })

关键点二:签名后把主机名换成自定义域名。因为 OSS 的 V1 签名不含 host,换域名后签名依然有效;而 R2 的 SigV4 签名绑定 host,不能换(代码里已注明):

def url(self, key, sign_secs=None):
    if sign_secs:
        u = self.bucket.sign_url("GET", key, sign_secs)
        # 绑了自定义域名就把主机名换过去:默认域名会强制下载,必须换
        if self.public_host and self.public_host != self.default_host:
            parts = urlsplit(u)
            u = urlunsplit((self.public_scheme, self.public_host,
                            parts.path, parts.query, parts.fragment))
        return u

4.7 短链:一个公开的”壳”指向私有的真实文件

这是让链接变短、变固定的关键。壳就是一个几十字节的 HTML,用 iframe 加载真实页面,浏览器地址栏始终显示短链,客户看不到签名串:

SHORT_HTML = """<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8">
<title>{title}</title>
<style>html,body{{margin:0;height:100%;overflow:hidden}}</style>
</head><body><iframe src="{url}"></iframe></body></html>
"""

def make_shortcut(backend, short_key, real_url, title, use_jump):
    tpl = JUMP_HTML if use_jump else SHORT_HTML
    backend.put_bytes(short_key, html.encode("utf-8"),
                      "text/html; charset=utf-8", public=True)

为什么能这么做:桶的”阻止公共访问”是关闭状态,所以单个对象可以设公共读(已实测确认)。真实看板文件仍然私有,只有壳是公开的——壳里除了一个签名地址什么都没有。

续期逻辑:真实文件的签名默认 30 天(shortSign=30d)。到期后重跑同一条命令,壳里的地址会更新,而给客户的链接一个字都不变。

4.8 无人值守:让 AI 能代替你跑完

用户说”我不懂程序,你来运行工具、一步步向我要我需要的东西”,所以脚本必须能在没有键盘输入的环境下跑完。

加 –yes 参数,所有提问自动选默认值;

判断 _unattended():AUTO_YES 或 sys.stdin 不是 tty 时,所有 ask() 直接短路返回默认。

Windows 的 getpass 会绕过管道直接读键盘——AI 用管道调用时会永久卡住(实测卡过一次)。所以必须先判定无人值守再短路,不能只靠 try/except 兜底。

第 5 章 操作 SOP(照着做就能跑通)

5.1 环境准备

确认 Python 与依赖。注意:依赖装在哪个解释器上,就必须用哪个解释器跑,用错会 ModuleNotFoundError。

# 本例使用系统 Python 3.12(oss2 / boto3 装在这里)
C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe -m pip install oss2 boto3

# 验证
python -c "import oss2, boto3; print(oss2.__version__, boto3.__version__)"
# 期望输出类似:2.19.1 1.43.103

5.2 填写密钥.txt

打开 工具/看板发布/密钥.txt,只需要你手填的部分(其余已按私有交付配好):

backend = oss

oss.endpoint = https://oss-cn-hangzhou.aliyuncs.com
oss.accessKeyId = 你的 RAM 用户 AccessKeyId
oss.accessKeySecret = 你的 RAM 用户 AccessKeySecret
oss.bucket = accunion-dashboard
oss.publicHost = kanban.accunion.cn

public = false # 私有 + 签名
single = true # 打包成自包含单文件
short = true # 生成固定短链
shortSign = 30d # 短链背后真实文件的签名时效
jump = false # 用 iframe,地址栏保持短链

写注释请用独立的 # 行。不要写成 oss.accessKeyId = ← 在这里填 —— 解析器会把提示语当真值(踩过,见 7.10)。

5.3 登记看板(加两行)

board.小园香径.BOM成本差异.dir    = 客户档案/003、小园香径独徘徊(个人客户)/2、BOM成本差异分析/看板
board.小园香径.BOM成本差异.prefix = c003/bom

确认登记成功:

python 工具/看板发布/publish.py --list

5.4 首次对接自检(只跑一次)

python 工具/看板发布/init.py

有密钥文件时它是免交互的:读文件 → 缺字段就明确报 → 测连通 → 测读写 → 测签名 → 写配置。期望看到:

✓ bucket 已存在(oss-cn-hangzhou)
✓ 读写权限正常
✓ 签名 URL 可生成

如果报 AccessDenied 或 “does not belong to you”,回看 3.5 的排错表——大概率是 RAM 用户还没挂策略。

5.5 发布(以后每次就这一条)

$PY = "C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe"
& $PY 工具/看板发布/publish.py --client 小园香径

真实输出(本例):

密钥文件:密钥.txt 后端=oss 登记看板 2 个
客户:小园香径 共 1 个看板 后端:阿里云 OSS

■ BOM成本差异 c003/bom/ 1 个文件 私有 + 签名 30d + 固定短链
------------------------------------------------------------
  ↑ index.html → c003/bom/index.html  (136.6 KB)
  给客户:https://kanban.accunion.cn/c003/bom

客户总入口(1 个看板):https://kanban.accunion.cn/c003
============================================================
给客户的链接:
  BOM成本差异        https://kanban.accunion.cn/c003/bom
  (总入口)          https://kanban.accunion.cn/c003

先演练(不连服务器、不校验密钥,确认要传什么):

python 工具/看板发布/publish.py --client 小园香径 --dry-run

5.6 验收清单(发布后务必自查)

用下面这段脚本检查,9 项全过才算交付合格:

import urllib.request, re
u = "https://kanban.accunion.cn/c003/bom"
r = urllib.request.urlopen(u, timeout=20); b = r.read()
print("HTTP", r.status, "|", len(b), "字节 |", r.headers.get("Content-Type"))
print("强制下载头:", r.headers.get("x-oss-force-download") or "无 ✅")
print("Content-Disposition:", r.headers.get("Content-Disposition") or "无 ✅")
# 再取壳里的真实地址,确认真实页能加载
检查项 期望结果 不合格说明
HTTP 状态 200 403=签名过期或没权限;404=prefix 写错
Content-Type text/html; charset=utf-8 若缺 charset,中文会乱码
x-oss-force-download 不存在 存在=还在用默认域名,客户会下载文件
Content-Disposition 不存在 同上
页面大小 与本地 dist/index.html 一致 不一致=单文件没打包完整(漏内联了某个 js)
含数据变量 能搜到 window.COST 之类 搜不到=data.js 没内联进去
HTTPS 无告警 浏览器显示锁标志 报不匹配=证书没”部署”(见 7.3)

5.7 把链接发给客户

发短链即可。建议同时说明两点:① 链接 30 天内有效,我们每月会续期,链接不变;② 数据保密,请勿转发。

第 6 章 多客户、多看板怎么放

6.1 云端目录结构(一个桶装所有客户)

accunion-dashboard/
├── c002/ ← 客户码
│ ├── sales/index.html 真实看板(私有,签名访问)
│ ├── sales 短链壳(公开读)→ kanban.accunion.cn/c002/sales
│ └── (c002) 客户总入口 → kanban.accunion.cn/c002
├── c003/
│ ├── bom/index.html
│ ├── bom → kanban.accunion.cn/c003/bom
│ └── (c003) → kanban.accunion.cn/c003
└── …以后新客户继续加 c004/、c005/

6.2 本地目录 ↔ 云端的映射(哪些传、哪些不传)

只上传”看板目录”这一层。客户的原始数据、清洗脚本、数据集、需求文档一律不上传——里面是客户敏感底稿,本来也不该出本机。

本地 云端 说明
客户档案/002、…/001、做销售看板/看板/ c002/sales/ 目录 ↔ 前缀,整目录对应
└ dist/index.html c002/sales/index.html ★ 实际传输物(单文件模式只传这一个)
原始数据/ 、清洗/ 、数据集/ — 不上传
客户档案/003、…/2、BOM成本差异分析/看板/ c003/bom/ 同上
1、客户资料/(含需求文档) — 不上传
客户档案/001、江西元气谷/002、资金看板/ — 未登记,内部自用,不上传

两种模式的上传规则:

模式 传什么 落到云端
单文件(默认,single=true) 只传 看板/dist/index.html /index.html
多文件(–no-single) 看板目录下所有文件 /<各自相对路径>
两种都跳过 dist/、.git/、node_modules/、pycache/ —

6.3 新增客户 / 新增看板:只改两行

# 新客户
board.某某公司.成本分析.dir    = 客户档案/004、某某公司/1、成本分析/看板
board.某某公司.成本分析.prefix = c004/cost

# 给老客户再加一个看板
board.小园香径.预算执行.dir    = 客户档案/003、…/3、预算执行/看板
board.小园香径.预算执行.prefix = c003/budget

登记后 –client 小园香径 会一次发两个,总入口自动列出两项,不用改任何代码。

6.4 日常运维

事项 频率 做法
更新看板数据 客户数据更新时 替换 data.js → 重跑 publish 命令,客户刷新页面即可
短链续期 每 30 天 重跑同一条 publish 命令,客户链接不变
证书续签 到期前(本例 2026-12-28) 证书控制台续签,并重新”部署到云资源”——只续签不部署会失效
检查桶内对象 偶尔 publish.py –list 查看登记;OSS 控制台查看实际对象

第 7 章 踩坑实录(按踩到的顺序)

这一章是全文最有价值的部分。每一条都是真实发生过、并已验证解决办法的。

7.1 默认域名强制下载(致命)

现象:客户点链接,浏览器直接下载一个 html 文件而不是打开看板。

根因:OSS 默认域名返回的响应头里有 x-oss-force-download: true 和 Content-Disposition: attachment。

试过且无效的两条路:① 签名加 response-content-disposition=inline;② 对象元数据设 Content-Disposition: inline。都会被 OSS 覆盖回 attachment。

解决:绑定自定义域名(需备案)。绑定后这两个头消失,浏览器正常预览。

7.2 没有备案域名怎么办

把单文件 HTML 直接发客户,客户双击用浏览器打开(最省事,但更新麻烦);

接受”下载后打开”(体验差,不推荐);

用境外 bucket(R2 有 r2.dev 域名,但数据出境 + 国内访问慢,只适合公开样板);

先去备案(一两个星期,长期看最划算)。

7.3 HTTPS 证书”已签发”≠ 已生效

现象:证书控制台明明显示”已签发”,浏览器访问 https 却报 Hostname mismatch / CERTIFICATE_VERIFY_FAILED。

根因:签发只是把证书造出来,OSS 并不会自动用它。必须再执行一次”部署到云资源”,把它们关联起来。证书列表里的”已部署”列如果是 –,就是没部署。

解决:数字证书管理服务 → 该证书 → 部署 → 云产品部署 → 选 OSS / 桶 / 域名。oss2 没有这个接口,只能控制台操作。

7.4 部署后仍然时好时坏

现象:部署完成后测 10 次,有 3 次失败,报证书不匹配。

根因:同一个 IP 交替返回两张证书(一张含你的域名、一张是 OSS 默认证书)——证书正在节点间灰度生效。

诊断方法:连续握手多次,比对证书的 SHA256 前缀,如果指纹在两张之间跳变,就是灰度未收敛。

解决:等。本例约 20 分钟后复测 30/30 全通。

7.5 私有交付不打单文件 = 白屏

现象:签名链接能打开,但页面空白,控制台一片 403。

根因:签名只签了 index.html 这一个 key,页面里的相对引用(css/js/data)没有签名,全部被拒。

解决:build_single.py 内联成单文件。脚本已加兜底:非公开且没显式 –no-single 时自动打包。

7.6 ”阻止公共访问”与短链的冲突

现象:短链壳设 public-read 失败。

根因:桶开了”阻止公共访问”,对象级 ACL 会被拒。

本例实况:创建时界面显示”已开启”,但后来 API 实测为 False(关闭),所以单对象可以设公共读,短链才能工作。

结论:短链依赖这个开关为”关闭”。若哪天控制台把它打开,短链壳会失效,需要改回或退回用签名长链接。判断依据以 API 实测为准,不要只看截图。

7.7 配置读了两套,改了不生效

现象:在密钥.txt 里改了 publicHost,发布出来的链接还是默认域名。

根因:load_config() 只读 publish_config.json,没合并密钥文件。

解决:让 load_config 接收已合并的配置作为 base。教训:新增配置源时,要检查所有读配置的函数,不能只改一处。

7.8 布尔参数语义反转

现象:密钥里写 jump = false(想要 iframe 保持地址栏),结果生成的是跳转页。

根因:函数参数名叫 use_iframe,实际接收的却是 jump 的值,判断写成 if use_iframe is False → 语义反了。

解决:参数名与传入值语义必须一致(改成 use_jump 并修正判断)。

7.9 驼峰键名被压成小写

现象:密钥文件里写了 shortSign,解析器认不出来。

根因:键名规范化函数对不含连字符的键会整体小写(shortSign → shortsign),与字段表匹配不上。

解决:新增驼峰字段必须同时登记到 CANON 映射表。

7.10 注释写法导致提示语被当真值

现象:密钥文件里写 oss.accessKeyId = ← 在这里填,脚本把”← 在这里填”当成了密钥。

解决:提示语必须写成独立的 # 注释行;同时解析器加防呆(← 开头或 <…> 包裹的视为空值)。

7.11 旧配置残留干扰合并

现象:登记改成两级后,–client 小园香径 命中的仍是旧的一级登记(旧前缀 003-小园香径)。

根因:publish_config.json 里还留着旧记录,被合并进来了。

解决:清空它的 boards,让密钥.txt 成为唯一登记来源。

7.12 Windows 中文乱码与 BOM

控制台中文乱码 → 脚本开头调 SetConsoleOutputCP(65001);

JSON 读不出来(Unexpected UTF-8 BOM)→ 记事本/PowerShell 写文件会带 BOM,一律用 utf-8-sig 读。

7.13 getpass 在管道下永久卡住

Windows 的 getpass 绕过管道直读键盘,AI 用管道调用脚本会一直等着。必须在调用前判定无人值守并短路,不能只靠 try/except。

7.14 R2 的签名不能换域名

OSS 的 V1 签名不含 host,所以签名后可以换成自定义域名;R2/S3 的 SigV4 签名绑定 host,换了会 SignatureDoesNotMatch。代码里已分别注明,别混用。

第 8 章 附录

附录 A 密钥.txt 完整示例

backend = oss

oss.endpoint        = https://oss-cn-hangzhou.aliyuncs.com
oss.accessKeyId     = LTAI********************
oss.accessKeySecret = ************************
oss.bucket          = accunion-dashboard
oss.publicHost      = kanban.accunion.cn

public    = false
sign      = 7d
single    = true
short     = true
shortSign = 30d
jump      = false
website   = false
open      = false

board.小园香径.BOM成本差异.dir    = 客户档案/003、小园香径独徘徊(个人客户)/2、BOM成本差异分析/看板
board.小园香径.BOM成本差异.prefix = c003/bom

board.巴基斯坦OPPO.销售看板.dir    = 客户档案/002、巴基斯坦oppo/001、做销售看板/看板
board.巴基斯坦OPPO.销售看板.prefix = c002/sales

附录 B 常用命令速查

$PY = "C:/Users/Administrator/AppData/Local/Programs/Python/Python312/python.exe"

# 查看已登记的客户 / 看板
& $PY 工具/看板发布/publish.py --list

# 演练(不上传)
& $PY 工具/看板发布/publish.py --client 小园香径 --dry-run

# 发布某个客户的全部看板(最常用)
& $PY 工具/看板发布/publish.py --client 小园香径

# 只发其中一个看板
& $PY 工具/看板发布/publish.py --client 小园香径.BOM成本差异

# 首次对接自检
& $PY 工具/看板发布/init.py

# 临时发一个没登记的目录
& $PY 工具/看板发布/publish.py --dir "<看板目录>" --prefix c999/demo --short

附录 C 本次实战的关键数据

项目 取值
Bucket accunion-dashboard(华东1 杭州,私有,低频访问存储)
Endpoint oss-cn-hangzhou.aliyuncs.com
自定义域名 kanban.accunion.cn(解析 118.31.219.204)
HTTPS 证书 cert-bdo9y / 2753078-cn-hangzhou,2026-09-29 签发,2026-12-28 到期
看板 003 c003/bom/index.html 136.6 KB (本地 ↔ 云端逐字节一致)
看板 002 c002/sales/index.html 2631.0 KB(含 143 KB 地图数据内联)
实测结果 HTTPS 200|text/html; charset=utf-8|无强制下载头|30/30 次访问成功
依赖版本 Python 3.12 oss2 2.19.1 boto3 1.43.103

附录 D 参考来源

本文档基于一次完整实战整理,原始记录见本仓库:

聊天对话记录/20260928.md —— 看板发布工具从零到首次上传成功的全过程

聊天对话记录/20260929.md —— 多客户多板结构、短链设计、HTTPS 打通、证书部署

工具/看板发布/使用说明.md —— 命令与参数的完整说明

附件/20260928/、附件/20260929/ —— 本文所用控制台截图原件

本文档可用 node 工具/看板发布/gen_docx.js 重新生成。

© 版权声明
THE END
喜欢就支持一下吧
点赞9867 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容