# 奇点CMS 打包说明
每次修复 BUG 后进行全新打包时,必须按本说明逐项执行,确保交付包无硬编码残留、任何客户均可全新安装。
一、打包前检查清单
1. 硬编码凭据扫描(必须零残留)
打包前必须执行全站扫描,确认以下敏感信息零残留:
cd 网站根目录
# 扫描所有已知的开发者个人凭据(将 PATTERN 替换为实际需要排查的值)
# 建议维护一个 .forbidden-patterns 文件,每行一个正则,用 grep -f 批量扫描
grep -rn 'YOUR_FORBIDDEN_PATTERNS_HERE' . \
--include='*.php' --include='*.js' --include='*.json' --include='*.go' \
--include='*.md' --include='*.txt' --include='*.html' --include='*.css'
# 预期输出:无任何匹配行
# 如有匹配,必须清理后重新扫描,直到零残留
# 注意:本文件(打包说明.md)中的示例仅作说明用途,不作为硬编码残留
运行时密钥泄漏比对(必须执行):仅靠正则模式无法发现"客户真实凭据被写进源码"的情况。 打包前必须用运行环境中的实际密钥值对暂存目录做程序化比对,只报告命中文件数,不回显明文:
# 比对源:config/generated.php 中的 DB_PASS、data/.pool_key、data/.license_domain
# 方法:读取上述值 → 逐一在打包暂存目录所有文件中查找子串 → 输出命中文件列表
# 预期:命中数为 0。任何命中都必须清理后重新比对
通用敏感模式扫描(必须执行):对暂存目录全量文件扫描以下正则,预期零命中:
| 模式 | 正则 | 说明 | |
|---|---|---|---|
| API Key | sk-[A-Za-z0-9_\-]{16,} | 任何 sk- 开头的密钥 | |
| 服务器 IP | 开发/演示服务器IP段正则(由打包人维护,勿在本文档写明文) | 开发/演示服务器地址不得进入交付包 | |
| 私钥块 | `BEGIN (RSA \ | EC )?PRIVATE KEY` | 任何私钥文件内容 |
2. 安装向导默认值检查
以下文件必须保持通用默认值,不得出现任何客户特定凭据:
| 文件 | 检查项 | 合规标准 |
|---|---|---|
install/index.php | 数据库表单默认值 | host=127.0.0.1, port=3306, user=root, pass=空, name=dream_cms |
install/index.php | 管理员表单默认值 | username=空, password=空, email=空 |
install/auto-install.php | 环境变量默认值 | DB_USER/DB_PASS/ADMIN_USER/ADMIN_PASS 默认为空字符串,缺失时报错退出 |
install/auto-install.php | CLI 输出 | 不得在 echo 中输出明文密码 |
install/auto-install.js | CONFIG.db.pass | 空字符串 |
install/auto-install.js | CONFIG.admin | username/password/email 均为空字符串 |
3. 敏感文件排除检查
打包前确认以下文件不在交付包中(如存在必须删除):
| 文件/目录 | 原因 |
|---|---|
config/generated.php | 包含客户数据库凭据,仅运行环境生成 |
install.lock / install/install.lock | 安装锁定标记,仅运行环境生成 |
install/index.html | 安装完成后生成的"已安装"跳转占位页,须由 install/index.php 安装向导替代 |
data/.license_domain | 客户授权域名绑定文件,仅运行环境生成 |
data/.pool_key | 号码池密钥,仅运行环境生成 |
data/number_pool_*.dat | 号码池运行时数据,安装时重新生成 |
.user.ini | 宝塔面板运行时防跨站配置,非源码 |
.maintenance | 系统更新维护模式标记,仅运行时存在 |
*.bak / *.orig / *.tmp | 备份/临时文件,不应进入交付包 |
站点根目录遗留的 *.zip | 历史交付包不得嵌套进新包 |
| 站点根目录的开发遗留文件 | audit-report.json、AUDIT-REPORT-v0.3.54.md、scan_schema.py、scan_schema_v2.py、聊天修复说明.txt:内部审计报告 / 结构扫描脚本 / 开发笔记,均非交付内容;后续新增同类文件须一并登记到本节与本项打包脚本 |
uploads/ 内用户上传内容 | 运行时数据,不属于源码包;仅保留默认占位资源(uploads/agent-icons/agent-default.png 与 uploads/.gitkeep)。agent-default.png 必须保留:它是三处 getSetting('icon_agent', ...) 的代码默认值,整目录清空会导致全新安装后 Agent 悬浮球/聊天头像 404(v0.3.84 包即因此缺件,v0.3.85 已补齐并列入下方打包后验证清单) |
注意:
packages/目录本身必须随包交付(靠packages/index.html+packages/.htaccess占位文件保证目录存在,全新部署后在线更新功能才可用);目录内的任何*.zip更新包仍按上表排除。
4. PHP 语法检查
# 对所有修改过的 PHP 文件执行语法检查
find . -name '*.php' -exec php -l {} \; | grep -v 'No syntax errors'
# 预期输出:无任何行(全部通过)
宝塔环境注意:宝塔的 PHP CLI 默认加载 php.ini,ionCube / OPcache / JIT 可能与
php -l 冲突导致进程崩溃(terminated by signal 11),且崩溃会被误判为通过。
必须使用 -n(不加载 ini)参数:
# 宝塔环境标准做法(PHP 路径按实际版本调整)
fail=0; for f in $(find . -name '*.php'); do
out=$(/www/server/php/82/bin/php -n -l "$f" 2>&1)
case "$out" in *"No syntax errors"*) ;; *) fail=$((fail+1)); echo "FAIL: $f";; esac
done; echo "failed=$fail"
# 预期输出:failed=0
如环境中无 PHP CLI,可用括号配对脚本替代(见本文末附录)。
5. 安装向导文件完整性检查(易漏!)
线上站点安装完成后,安装向导三件套会被自动移除,仅留下 install/index.html("已安装"
跳转占位页)和 install.lock。直接打包线上目录会得到一个无法全新安装的废包。
打包前必须确认以下文件存在于打包暂存目录,缺失时从上一版交付包中恢复:
| 文件 | 作用 |
|---|---|
install/index.php | Web 安装向导 |
install/auto-install.php | CLI 自动安装脚本 |
install/auto-install.js | 浏览器自动填表脚本 |
# 从上一版交付包恢复向导文件(包名按实际调整)
unzip -o -j "上一版交付包.zip" install/index.php install/auto-install.php install/auto-install.js -d install/
恢复后必须做兼容性核对:对比线上 database.sql、install/*-migration.sql 与上一版
包内同名文件是否一致。若数据库结构已变化而向导仍按旧结构建表,必须先同步更新向导或
SQL 文件,再进入打包流程。
6. 依赖与文档同步检查(必须)
凡本版本新增/变更了扩展、系统依赖、服务器配置或数据表结构,打包前必须同步以下三处, 缺一不得进入打包流程:
| 位置 | 同步内容 |
|---|---|
includes/SystemRequirements.php | checks() 注册表追加检测项(含状态判定逻辑与修复建议) |
docs/安装配置说明.md | 依赖清单 / php.ini / nginx / settings 配置项说明 |
docs/版本升级说明.md | "版本依赖变更历史"追加本版本条目(变更点 + 升级动作 + 是否需手动) |
打包前登录后台「系统 → 部署检测」跑一遍全站检测,确认无"不通过"项;两份文档已列入 "打包后验证"的关键文件清单,缺失会被校验脚本标出 MISSING。
7. Agent+ 组件交付检查(部署即用)
Agent+ 采用双实现:Go 常驻服务不可用时由 PHP 微内核自动接管(功能一致、性能降低)。 故交付包只需保证 PHP 侧开箱可用,Go 服务是可选加速项——客户部署后检测页对该组显示 "警告/信息"属预期状态,不得据此判定交付缺陷。详细交付形态与客户部署步骤见第七节。
| 类别 | 打包要求 |
|---|---|
| 必须随包交付 | go-service/(*.go、go.mod、go.sum、README.md)——源码交付,客户自行编译 |
| 必须随包交付 | database.sql 的 4 张知识库表(agent_knowledge / _fragments / _tags / _hits,含 FULLTEXT ... WITH PARSER ngram):全新安装即建好,无需额外迁移 |
| 必须排除 | Go 编译产物(x-dream-go、*.exe)、go-service/vendor/、任何模块/构建缓存(gocache、gomod) |
| 必须排除 | /opt/xdream-go/ 下的产物副本、systemd 单元实例 /etc/systemd/system/x-dream-go.service、服务日志 |
| 严禁写入 | GO_API_KEY 实际值(只允许存在于客户服务器的 systemd 单元 Environment=;Go 侧复用站点 config/ 连库,不另存口令) |
| 一致性 | includes/SystemRequirements.php 的 agent 组 9 项须与 docs/安装配置说明.md 第五节及本文档第七节的部署口径一致 |
| 一致性 | 安装向导(install/auto-install.php、install/index.php)重装时只重写 DB_*,其余配置项(含 GO_API_KEY)保留 / 从 Go 侧继承,不得整文件覆盖导致两侧密钥不一致 |
开发者环境专项:
go-service/config.go中XDREAM_CONFIG_DIR的默认值指向开发者站点目录, 交付文档(第七节单元模板)必须写明由客户改为其真实站点config/路径,否则客户侧 Go 服务连不上库。
8. 官网组件与对接链路保留检查(不得剥除)
站点与官网系统之间存在商业授权、插件市场、发布包查询、塔台心跳等多条对接链路, 打包时下列对象不得剥除,必须随包完整交付:
| 对象 | 说明 |
|---|---|
official/ 目录 | 官网系统源码(含 official/api.php 的 ping/plans/plugins/activate/verify/deactivate/query 等接口);官网可作主站子目录部署,如 /www/wwwroot/i5/official/ |
includes/LicenseManager.php | 商业授权 + 塔台心跳对接官网的接口实现(license_api 等设置键) |
admin/license-online.php | 后台「商业授权 / 官网连接」配置入口 |
official/docs/官网部署说明.md | 官网独立部署与接口对接文档 |
与官网相关的配置接口、链接、API 一律不能剥除;
/www/wwwroot/i5(含official/子目录,即官网作为主站子目录部署的路径)须随包保留,不得当作非交付内容剥离。
一点五、前端资源发布顺序(IM 插件强缓存)
assets/im-plugin/** 被 nginx 强缓存(JS/CSS 12h/30d),其缓存令牌是 assets/js/tab-card.js 中的 IM_BUILD 常量。
规则:改动 assets/im-plugin/** 时,assets/js/tab-card.js(IM_BUILD 已递增的那份)必须与插件改动同批或先行上线。
反之(tab-card.js 晚于 im-sdk.js/chat.js 部署)会出现一个窗口:老用户仍在强缓存里执行旧插件包, 表现为"改了但没生效"。实测曾出现约 2 分钟的此类窗口。
二、打包流程
1. 准备工作
1. 确认所有 BUG 修复已完成并通过测试
2. 执行上述"打包前检查清单"全部五项,全部通过
3. 递增版本号:修改 config/config.php 中的 CMS_VERSION(如 v0.3.68 → v0.3.69)
包命名规范:QDCMS-v{CMS_VERSION去掉v前缀的完整版本号}.zip,与 CMS_VERSION 严格一致。
示例:CMS_VERSION 为 v0.3.69 时,交付包命名为 QDCMS-v0.3.69.zip。
交付包统一输出到 /www/wwwroot/ 下。
版本号递增只应体现在交付包(或经确认后的线上源码)中;未经确认不得顺手改动线上运行站点。
2. 打包命令
cd 打包暂存目录的上级目录 # 暂存目录 = 按检查清单清理后的站点副本,勿直接打包线上目录
# 打包(排除运行时生成的文件)
python3 -c "
import zipfile, os
zip_path = 'QDCMS-v{版本号}.zip'
exclude_dirs = {'.git', '__pycache__', '.dumate-data'}
exclude_files = {'config/generated.php', 'install.lock', 'install/install.lock',
'install/index.html', 'data/.license_domain', 'data/.pool_key',
'.user.ini', '.maintenance',
# 站点根目录开发遗留文件(内部审计报告 / 扫描脚本 / 开发笔记)
'audit-report.json', 'AUDIT-REPORT-v0.3.54.md', 'scan_schema.py',
'scan_schema_v2.py', '聊天修复说明.txt'}
exclude_exts = {'.bak', '.orig', '.tmp'}
exclude_globs = ('number_pool_',) # data/number_pool_*.dat 运行时数据
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk('暂存目录名'):
dirs[:] = [d for d in sorted(dirs) if d not in exclude_dirs]
for f in sorted(files):
if any(f.endswith(ext) for ext in exclude_exts):
continue
if f.endswith('.zip') or f.startswith(exclude_globs):
continue
rel = os.path.relpath(os.path.join(root, f), '暂存目录名')
if rel in exclude_files:
continue
zf.write(os.path.join(root, f), rel)
print(f'打包完成: {zip_path} ({os.path.getsize(zip_path)} bytes)')
"
3. 打包后验证
# 验证 zip 完整性
python3 -c "
import zipfile, re
zf = zipfile.ZipFile('QDCMS-v{版本号}.zip', 'r')
assert zf.testzip() is None, 'zip 完整性校验失败'
names = zf.namelist()
print(f'文件总数: {len(names)}')
# 检查关键文件存在
for key in ['install/index.php', 'install/auto-install.php', 'install/auto-install.js',
'config/config.php', 'database.sql', 'messages.php', 'README.md',
'nginx.conf.example', 'docs/打包说明.md',
'docs/安装配置说明.md', 'docs/版本升级说明.md',
'admin/system-requirements.php', 'includes/SystemRequirements.php',
'go-service/README.md', 'go-service/main.go', 'go-service/go.mod',
'go-service/go.sum',
'packages/index.html', 'packages/.htaccess',
'assets/images/default-avatar.png', 'uploads/agent-icons/agent-default.png',
'qdcms-go-win.ps1', 'go-service/run-xdream-go.ps1', 'go-service/register-task.ps1',
'qdcms-ffmpeg-win.ps1', 'qdcms-selfcheck.ps1', 'qdcms-deploy.ps1',
'sites.example.json', 'docs/Windows部署说明.md', 'docs/聊天即时通信说明.md',
'ajax-chat-history.php', 'ajax-im-token.php', 'assets/im-plugin/modules/chat.js',
'assets/im-plugin/core/im-sse.js', 'go-service/im_hub.go', 'go-service/im_sse.go',
'docs/客户安装验收清单.md', 'docs/传输优化说明.md',
'install/optimize-static-images.php', 'assets/images/qdcms-logo.png', 'favicon.png']:
print(f' {key}: {\"OK\" if key in names else \"MISSING\"}')
# 检查敏感文件不存在
for bad in ['config/generated.php', 'install.lock', 'install/install.lock',
'install/index.html', 'data/.license_domain', 'data/.pool_key',
'.user.ini', '.maintenance',
'audit-report.json', 'AUDIT-REPORT-v0.3.54.md', 'scan_schema.py',
'scan_schema_v2.py', '聊天修复说明.txt']:
print(f' {bad}: {\"未排除!\" if bad in names else \"已排除 OK\"}')
# 检查 Go 编译产物未入包(源码交付,产物由客户在目标机编译)
for bad in ['go-service/x-dream-go', 'go-service/x-dream-go.exe']:
print(f' {bad}: {\"未排除!\" if bad in names else \"已排除 OK\"}')
print(' 含 go-service 条数:', len([n for n in names if n.startswith('go-service/')]))
# 检查包内版本号与包名一致
ver = re.search(rb\"CMS_VERSION', '([^']+)'\", zf.read('config/config.php')).group(1).decode()
print(f'包内版本号: {ver}(须与包名中的版本号一致)')
"
三、硬编码禁止清单
以下信息严禁以明文形式出现在任何交付文件中:
| 类别 | 说明 | 允许的处理方式 |
|---|---|---|
| MySQL root 密码 | 任何实际数据库密码 | 安装向导由客户手动输入;CLI 脚本从环境变量读取 |
| 管理员用户名 | 任何实际管理员用户名 | 安装向导由客户手动输入;CLI 脚本从环境变量读取 |
| 管理员密码 | 任何实际管理员密码 | 安装向导由客户手动输入;CLI 脚本从环境变量读取 |
| 管理员邮箱 | 任何实际邮箱地址 | 安装向导由客户手动输入;CLI 脚本从环境变量读取 |
| 开发者 QQ 号 | 任何实际 QQ 号 | 占位符如"请在后台配置" |
| 开发者手机号 | 任何实际手机号 | 占位符如"请在后台配置" |
| 开发者微信号 | 任何实际微信号 | 占位符如"请在后台配置" |
| 开发者邮箱 | 任何实际邮箱 | 占位符如 support@yourdomain.com |
| API Key / Token / 密钥 | 任何以 sk- 开头的 key、token、密码、连接串等 | 绝不写入任何文件;仅记录"它存在、应放在环境变量中" |
四、默认值规范
安装向导各字段的合规默认值如下,交付包中必须使用这些值:
数据库配置
| 字段 | 默认值 | 说明 |
|---|---|---|
| 主机 | 127.0.0.1 | 通用本地地址 |
| 端口 | 3306 | MySQL 默认端口 |
| 用户名 | root | 通用默认用户,客户可改 |
| 密码 | (空) | 客户必须手动填写 |
| 数据库名 | dream_cms | 通用名称,客户可改 |
管理员配置
| 字段 | 默认值 | 说明 |
|---|---|---|
| 用户名 | (空) | 客户必须手动填写 |
| 密码 | (空) | 客户必须手动填写 |
| 邮箱 | (空) | 选填 |
站点默认名称
| 配置项 | 位置 | 默认值 | 说明 |
|---|---|---|---|
站点名称 SITE_NAME | config/config.php | 奇点CMS | 打包的站点默认名称为"奇点CMS",交付包必须使用该通用名称;客户个性化站名由客户安装后自行修改,不得将任何客户特定站名带入交付包 |
生效版本:自 v0.3.80 起。v0.3.79 及更早的交付包沿用历史站名(如"墨香书阁"),属已确认接受的例外,不做追溯重建。
CLI 脚本 (auto-install.php)
所有凭据通过环境变量传入,不提供任何硬编码默认值。缺少必填项时报错并退出,提示正确用法。
五、每次修复 BUG 后的打包检查流程
步骤 1: 修复 BUG → 代码变更
步骤 2: 制作打包暂存目录(线上站点副本),恢复安装向导三件套(第一节第 5 项)并核对 SQL 兼容性
步骤 3: 执行硬编码与运行时密钥扫描(第一节第 1 项)→ 零残留
步骤 4: 检查安装向导默认值(第一节第 2 项)→ 全部合规
步骤 5: 检查敏感文件排除(第一节第 3 项)→ 已排除
步骤 6: PHP 语法检查(第一节第 4 项,宝塔环境用 php -n -l)→ 全部通过
步骤 7: 递增 CMS_VERSION,按命名规范执行打包命令(第二节第 2 项)
步骤 8: 打包后验证(第二节第 3 项)→ 关键文件齐全、敏感文件已排除、包内版本号与包名一致
步骤 9: Agent+ 组件交付检查(第一节第 7 项)→ go-service 源码随包、编译产物未入包、交付含第七节部署说明
步骤 10: 制作版本快照(见第六节,可选但建议)
步骤 11: 交付
任何一步未通过,不得进入下一步。
六、版本快照(还原点,区别于交付包)
交付包是纯净源码包(按本文档排除全部运行时文件),用于客户全新安装; 版本快照是完整还原点,用于将本站点回滚到某一版本,二者不可互相替代。
1. 快照内容
站点目录的完整副本,包含交付包中排除的全部运行时文件:
config/generated.php(数据库凭据)install.lock、install/index.htmldata/.license_domain、data/.pool_key、data/number_pool_*.datuploads/全部用户上传内容.user.ini等面板运行时文件
2. 快照命令与命名规范
# 命名:{站点名}_v{版本号}_snapshot_{时间戳}.zip,输出到面板备份目录
SNAP="/www/backup/site/{站点名}_v{版本号}_snapshot_$(date +%Y%m%d_%H%M%S).zip"
cd /www/wwwroot && zip -qry "$SNAP" {站点目录名}
# 校验完整性,并抽查包内版本号
unzip -t "$SNAP" | tail -1 # 预期:No errors detected
unzip -p "$SNAP" {站点目录名}/config/config.php | grep CMS_VERSION
3. 注意事项
- 快照含数据库凭据与授权文件,仅限服务器内部留存,严禁作为交付物发给任何客户
- 快照不包含 MySQL 数据;需要完整可回滚能力时,另行通过面板对站点关联数据库做备份
- 快照是时间点副本:制作后不得再向包内更新文件,否则失去还原点意义
七、Agent+ 组件交付与客户部署(部署即用)
Agent+ 采用双实现:Go 常驻服务不可用时由 PHP 微内核自动接管(功能一致、性能降低)。 交付包只需保证 PHP 侧开箱可用;Go 服务是可选加速项,客户按需启用即可, 未启用时检测页该组显示"警告/信息"属预期状态,不作为交付缺陷。
1. 交付形态
| 项目 | 交付形态 | 说明 |
|---|---|---|
go-service/(*.go、go.mod、go.sum、README.md) | 源码随包 | 编译产物与 CPU 架构 / Go 版本绑定,不得预编译入包 |
database.sql 的 4 张知识库表(agent_knowledge、agent_knowledge_fragments、agent_knowledge_tags、agent_knowledge_hits) | 随包 | 全新安装即建好,含 FULLTEXT ... WITH PARSER ngram(需 MySQL 5.7+;MariaDB 由 DbUpdater 自适应省略 parser,索引仍创建) |
编译产物 x-dream-go | 不入包 | 体积大且绑定构建环境,由交付方或客户在目标机编译 |
systemd 单元实例 /etc/systemd/system/x-dream-go.service | 不入包 | 属服务器运行态;模板见第 2 节,客户按真实路径填写 |
GO_API_KEY 实际值 | 不入包 | 仅允许存在于客户服务器的环境变量中 |
前端浏览器直连 Go(settings.go_service_url) | 默认关闭 | 保持留空,统一由 PHP GoAdapter 走 http://127.0.0.1:8080 回环调用 |
知识库表虽随包建好但不含任何知识条目:客户站点初始
agent_knowledge为 0 行属正常(检测页显示0 行即通过)。
2. 客户侧启用 Go 加速(可选,5 步)
# ① 安装 Go 1.21+(go.mod 声明 go 1.21);离线环境需先在目标机可用的模块代理下编译
# ② 在站点 go-service/ 目录编译
cd /www/wwwroot/<站点目录>/go-service
go build -o x-dream-go .
# ③ 安装产物
install -d -m 755 /opt/xdream-go
install -m 755 x-dream-go /opt/xdream-go/x-dream-go
# ④ 写入 systemd 单元(模板见下,务必按真实站点路径改 XDREAM_CONFIG_DIR)
vi /etc/systemd/system/x-dream-go.service
systemctl daemon-reload && systemctl enable --now x-dream-go
# ⑤ 验证
curl -s http://127.0.0.1:8080/health # 期望 {"cache":true,"database":true,"status":"healthy"}
systemctl is-active x-dream-go # 期望 active
单元模板(把 /www/wwwroot/<站点目录> 换为客户真实站点目录):
[Unit]
Description=X-Dream Agent+ Go Service
After=network.target mysql.service
[Service]
Type=simple
User=www
WorkingDirectory=/opt/xdream-go
Environment=GO_ADDR=:8080
Environment=GO_ALLOWED_IPS=127.0.0.1,::1
Environment=XDREAM_CONFIG_DIR=/www/wwwroot/<站点目录>/config
Environment=GO_CSRF_ENABLED=true
# 可选:启用 PHP↔Go HMAC 签名校验(不设置=仅回环信任模式)。
# 设置后必须在站点 config/generated.php 定义同名的 GO_API_KEY 且值完全一致,否则 PHP→Go 调用返回 401/403。
# Environment=GO_API_KEY=改成你自己的随机串
ExecStart=/opt/xdream-go/x-dream-go
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
交付时必须交代的四点:
XDREAM_CONFIG_DIR必须指向客户真实站点config/目录。go-service/config.go中的默认值 是开发者环境路径,不覆盖时服务会去读不存在的目录,表现为连库失败(检测页该项显示database=false)。- Go 侧不另存数据库口令:它按 PHP 的加载顺序解析
config/config.php+generated.php, 故这两个文件必须对运行服务的www用户可读(chmod 644),否则服务无法连库。 - 回环部署也应显式设置
GO_ALLOWED_IPS=127.0.0.1,::1:服务默认空白名单=放行所有来源, 仅在确有反向代理需求时才对外放开。 - 启用 HMAC 校验是双侧动作:单元里追加
Environment=GO_API_KEY=<随机串>后,站点config/generated.php必须有同名常量且值完全一致(安装向导会自动保留 / 从 Go 侧继承该值, 无需手工抄写)。两侧不一致时 PHP→Go 调用被拒(401/403)并静默回退 PHP 微内核—— 站点仍可用,仅 Go 加速失效,属最难察觉的部署缺陷,改后请跑一次「部署检测」确认密钥项为绿。
3. 不启用 Go 服务时的预期状态
| 检测页显示 | 是否影响客户使用 |
|---|---|
| 「Go 辅助服务可用性」警告·不可达 | 不影响:自动回退 PHP 微内核(功能一致、性能降低) |
| 「Go 服务部署产物 / 常驻与开机自启」警告·未部署 | 不影响 |
| 「PHP↔Go 共享密钥」信息 | 不影响:未启用 Go 时无需配置 |
| 「知识库数据表 / 中文全文索引」应为通过 | 若显示"不通过",须先做数据库结构同步(第一节第 6 项) |
客户验收动作:登录后台 → 系统 → 部署检测 → 确认 Agent+ 组无「不通过」项即可交付签收。
附录:无 PHP CLI 环境的替代语法检查
#!/usr/bin/env python3
"""PHP 括号配对检查脚本(无 PHP CLI 时替代 php -l)"""
import re, sys
def check_php(fp):
with open(fp, 'r') as f:
content = f.read()
i = 0; in_php = False; in_s = False; in_d = False
brace = 0; paren = 0
while i < len(content):
if not in_php and content[i:i+5] == '<?php':
in_php = True; i += 5; continue
if in_php and content[i:i+2] == '?>':
in_php = False; i += 2; continue
if in_php:
if not in_s and not in_d:
if content[i:i+2] == '//':
while i < len(content) and content[i] != '\n': i += 1
continue
if content[i:i+2] == '/*':
i += 2
while i < len(content) and content[i:i+2] != '*/': i += 1
i += 2; continue
if content[i] == "'": in_s = True; i += 1; continue
if content[i] == '"': in_d = True; i += 1; continue
if content[i] == '{': brace += 1
elif content[i] == '}': brace -= 1
elif content[i] == '(': paren += 1
elif content[i] == ')': paren -= 1
elif in_s:
if content[i] == '\\' and i+1 < len(content): i += 2; continue
if content[i] == "'": in_s = False
elif in_d:
if content[i] == '\\' and i+1 < len(content): i += 2; continue
if content[i] == '"': in_d = False
i += 1
return brace == 0 and paren == 0
if __name__ == '__main__':
for fp in sys.argv[1:]:
ok = check_php(fp)
print(f'{fp}: {"OK" if ok else "FAIL"}')