跳到主要内容
奇点CMS v0.4.66 · 官方网站 登录 / 注册 在线激活

奇点CMS 打包说明

打包前必须执行全站扫描,确认以下敏感信息零残留:

打包说明.md 文档版本 v0.4.66 27 KB 更新于 2026-10-11

# 奇点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 Keysk-[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.phpCLI 输出不得在 echo 中输出明文密码
install/auto-install.jsCONFIG.db.pass空字符串
install/auto-install.jsCONFIG.adminusername/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.phpWeb 安装向导
install/auto-install.phpCLI 自动安装脚本
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.phpchecks() 注册表追加检测项(含状态判定逻辑与修复建议)
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通用本地地址
端口3306MySQL 默认端口
用户名root通用默认用户,客户可改
密码(空)客户必须手动填写
数据库名dream_cms通用名称,客户可改

管理员配置

字段默认值说明
用户名(空)客户必须手动填写
密码(空)客户必须手动填写
邮箱(空)选填

站点默认名称

配置项位置默认值说明
站点名称 SITE_NAMEconfig/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.html
  • data/.license_domain、data/.pool_key、data/number_pool_*.dat
  • uploads/ 全部用户上传内容
  • .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

交付时必须交代的四点:

  1. XDREAM_CONFIG_DIR 必须指向客户真实站点 config/ 目录。go-service/config.go 中的默认值 是开发者环境路径,不覆盖时服务会去读不存在的目录,表现为连库失败(检测页该项显示 database=false)。
  2. Go 侧不另存数据库口令:它按 PHP 的加载顺序解析 config/config.php + generated.php, 故这两个文件必须对运行服务的 www 用户可读(chmod 644),否则服务无法连库。
  3. 回环部署也应显式设置 GO_ALLOWED_IPS=127.0.0.1,::1:服务默认空白名单=放行所有来源, 仅在确有反向代理需求时才对外放开。
  4. 启用 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"}')

原始文件:docs/打包说明.md · 出于安全考虑,站点不直接暴露 docs/ 目录下的原始文件(其中含内部资料),请在交付包内查看全文。

全部文档
站点公告 官网试运行中,如需购买或试用请通过「联系我们」留言。