Repository navigation
Releases: pollybird/zhycms
Release list
v3.1.3
性能与并发健壮性版本(GitCode issue #3 四项中危 M7-M10)
本版本针对 GitCode 仓库 issue #3 提出的四项性能与并发健壮性问题进行集中修复,无数据库结构变更、无新增依赖,覆盖代码重启即可生效。
M7 Setting 逐键查询写放大
单页渲染链路 Setting.get() 调用 20+ 次,每次一条 first() SQL,模板/上下文处理器叠加后写放大明显。
- 新增请求级整表缓存(
flask.g,app/models/setting.py::_request_cache_map):同请求首查整表建映射,后续读零 SQL set()写后失效当前请求,同请求内写后立即可见;get_dict()复用同一映射- 无应用上下文(CLI / 裸调用)回退逐键查询;缓存仅存活于单请求,多 worker 无一致性问题
M8 浏览量逐请求写事务 + 缓存窗口计数不增长
文章详情每次访问 viewed + 1 即 commit(),高并发下写库压力大;且整页缓存命中时视图体不执行,缓存 TTL 内(默认 900s)浏览量完全不增长。
- 改为缓存累加 + 合并回库:
_incr_view()把增量累加到缓存键article:viewed:pending(缓存不可用回落旧行为) - 新增
_flush_view_counts():以UPDATE ... SET viewed = viewed + n合并回库 - APScheduler 新增 60s 周期任务
zhycms_view_flush;多 worker(不启内置调度器)时由请求内阈值(累计 200 次)兜底触发 - 整页缓存命中路径显式调用
_incr_view(),修复缓存窗口内计数不增长 - 合并失败增量放回缓存不丢数;兜底 TTL 24h 防调度器长期停止时内存累积
M9 首页 N+1 查询
首页对 9 个行业区块 slug 逐个子查询(栏目 + 子栏目 + 文章各一条 SQL),叠加最新文章循环与导航树,单页 20+ 条 SQL。
- 收口为
_home_section_data()批量拉取:栏目一次IN、子栏目一次、跨栏目文章一次,共 3 条 SQL,内存分组取前 N,排序语义与原实现一致 Column.get_tree_cached()请求级缓存栏目树(导航 + 上下文处理器叠加只查一次)clear_content_cache()统一失效树缓存,栏目/文章 CRUD 后同请求立即读到新树
M10 用户名唯一性竞态
create_user 先查重后插入存在并发竞态,users.username 唯一索引兜底时 flush/commit 抛 IntegrityError 未捕获 → 500。
flush()与commit()均捕获IntegrityError,回滚并 flash「用户名已存在」业务错误
升级须知
- 无数据库结构变更、无新增依赖(APScheduler 已有),覆盖代码重启即可生效
- 浏览量统计语义变化:从「逐请求即时写库」变为「缓存累加 + 周期(约 60s)/ 阈值(200 次)合并」,计数为近似值(误差窗口 ≤ 60s 或 200 次);同时修复了开启整页缓存时计数不增长的缺陷
- 详见项目根目录
UPGRADE.md与CHANGELOG.md
测试与门禁
- 新增
tests/test_performance_m.py14 项场景测试(M7 SQL 计数与缓存失效、M8 批量合并/缓存页计数、M9 批量拉取 3 条 SQL/树缓存失效、M10 竞态回退/端点无 500) - 全量 360 项测试通过(v3.1.2 的 346 项 + 14 项新增)
scripts/check_release.py --version 3.1.3版本一致性检查通过scripts/check_constants.py常量门禁通过
致谢
感谢 GitCode 用户在 issue #3 中提出的性能优化建议。
v3.1.2
安全修复版本(GitCode issue #2 五项中危 M1-M5)
本版本针对 GitCode 仓库 issue #2 提出的五项中危安全问题进行集中修复,无数据库结构变更、无新增依赖,覆盖代码重启即可生效。
M1 表单输入保护(int → safe_int)
文章/栏目/碎片/搜索/分页/页面构建器等表单参数原本直接 int() 转换,非数字输入会抛 ValueError 被 Flask 兜底为 500,暴露异常栈并消耗服务器资源。
- 新增
app/utils/helpers.py::safe_int(value, default=0):strip + try/except 回退默认值 - 替换
article_service/column_service/fragment.py/article.py/views.py/builder/widgets/data.py等全部int()调用点 page/limit/per_page/offset等索引类参数追加max(1, ...),拒负数越界
M2 上传子目录白名单(路径穿越收口)
save_upload_file(sub_dir=...) 与 /admin/upload 的 type 参数原仅做拼接,攻击者可传 ../ 越界写入任意目录(如覆盖 app/static/ckeditor5/ckeditor.js 实施存储型 XSS)。
- 新增
app/utils/uploads.py::_safe_sub_dir(sub_dir):白名单common/image/file/avatar/editor/article/theme/site/column/fragment+ 段格式正则^[a-zA-Z0-9_-]{1,32}$ - 任一
..// 绝对路径 / 非白名单首段 / 非法段格式 均回退到common - 双层校验:
save_upload_file入口 +/admin/upload入口,纵深防御
M3 CKEditorFuncNum 反射型 XSS
富文本上传回调端点拼 <script>window.parent.CKEDITOR.tools.callFunction({func_num}, ...)</script>,func_num 原样透传未校验,攻击者可构造 1, document.cookie, " 注入任意 JS。
- 仅接受
^\d{1,6}$的CKEditorFuncNum,非法直接 400 file_url/err经html.escape(..., quote=True)后再拼入字符串
M4 SVG 上传管控
SVG 默认在上传白名单内(upload_allowed_exts 含 svg),但 SVG 可内嵌 <script> / onload= / <foreignObject> 等执行 JS,构成存储型 XSS。
- 新增设置项
upload_allow_svg,默认off;后台「上传安全」勾选后开启 - 开启后做内容嗅探:拒绝
<script>/onload=/onclick=/href="javascript:"/<foreignObject>/onerror=/onmouseover= - 512KB 大小上限,防 DoS
.svg静态响应强制Content-Type: image/svg+xml; charset=utf-8+X-Content-Type-Options: nosniff+Content-Security-Policy: sandbox; default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:,兜底防护存量 SVG- LOGO 上传同步联动:
upload_allow_svg=off时 LOGO 不接受 SVG
M5 登录验证码一次性消费
原登录流程仅在密码比对成功后才 session.pop('captcha'),比对失败时验证码留存,攻击者可同码反复提交枚举密码。
- 登录提交后无论密码比对结果先
session.pop('captcha', None),杜绝同码复用
升级须知
- 无数据库结构变更、无新增依赖,覆盖代码重启即可生效
- 曾使用 SVG 作为 LOGO / 文章配图的站点,需到后台「系统设置 → 上传安全」勾选「允许上传 SVG」
- 详见项目根目录
UPGRADE.md与CHANGELOG.md
测试与门禁
- 新增
tests/test_security_m.py23 项场景测试(M1 safe_int、M2 子目录白名单、M3 CKEditorFuncNum、M4 SVG 上传管控、M5 验证码一次性消费) - 全量 346 项测试通过(v3.1.1 的 320 项 + 26 项新增)
scripts/check_release.py版本一致性检查通过(CMS_VERSION / CHANGELOG / README / wiki.html 均为 3.1.2)scripts/check_constants.py常量门禁通过(无违规裸字面量)
致谢
感谢 GitCode 用户在 issue #2 中提出的安全建议。
v3.1.1
安全修复版本
安全修复版本(GitCode issue #1 高危项 H1 + H2)。API 鉴权改为默认拒绝(fail-closed),全站引入 CSRF 同步 Token 校验。无数据库结构变更、无新增 Python 依赖。
Fixed
- API 默认免鉴权(H1,高危):内容 API 静态令牌校验此前在「未配置令牌」时匿名放行,而默认安装
api_token为空,导致/api/v1/*全部端点可被任意匿名读取。现改为 fail-closed:未配置令牌时一律返回 401(提示到后台「内容 API」生成),配置令牌后校验X-API-Token(恒时比较)。 - 全站缺失 CSRF Token(H2,高危):新增 CSRF 同步 Token 机制(
app/utils/csrf.py):Token 与用户 Session 绑定,POST/PUT/PATCH/DELETE 强制恒时比较校验,未携带/错误返回 403;前后台所有模板注入<meta name="csrf-token">与自动提交捕获脚本(表单隐藏字段 + jQuery 请求头自动附加);手写 fetch/XHR(Word 导入、CKEditor 上传、页面构建器、一键翻译)逐处补齐X-CSRF-Token;/api/*与/healthz豁免;与既有 Origin/Referer 同源校验互为纵深。
Changed / 升级注意
- 使用内容 API 的站点升级后必须在后台「内容 API」生成令牌并以
X-API-Token请求头携带,否则返回 401(详见 UPGRADE.md)。 - 测试:新增 CSRF 防护 9 项、API fail-closed 5 项、迁移索引收口 4 项、删除确认验证码 3 项、审计清理 4 项用例,全量 320 项通过。
追加修复(v3.1.1 发布当日补丁,tag 已更新至包含全部修复)
- 后台无法登录(403 CSRF):
login.html/setup.html为不继承后台 base 的独立模板,缺少 csrf meta 与注入脚本——首次提交(验证码/密码输错)生成 Session Token 后,第二次提交必然 403。已为两个独立模板补齐 Token 渲染。 - 数据库迁移插件报 1709 索引超限:老版 MySQL(5.6/Antelope 或
innodb_large_prefix=OFF)单列索引前缀上限 767 字节,utf8mb4 下String(N)的 N>191 建索引即报错。迁移建表前统一把超限的索引/唯一列降到 191 字符,一次覆盖全部业务表与插件表。 - 删除主题 / 卸载插件必失败:确认弹窗此前未渲染验证码图片与输入框,提交必报「验证码已过期」。两个弹窗已补齐验证码 UI,每次打开或点击图片均重新取码。
- 审计日志清理范围失效:前端
days与后端keep_days字段名错位,所选清理范围被无视、永远按默认 90 天清理,「清空全部」从未生效。已对齐字段并支持days=0清空全部。
完整说明见 CHANGELOG.md 与 UPGRADE.md。
v3.1.0
ZhyCMS v3.1.0 — 数据库迁移插件
新增官方内置插件 db_migrator(默认不启用):在后台把 SQLite 开发库一键迁移到 MySQL / PostgreSQL——目标库建表 → 按外键依赖逐表复制数据(单事务失败整体回滚)→ PostgreSQL 自动重置自增序列 → 同步 Alembic 版本号(重启时不重复执行增量迁移);支持连接测试、目标库非空保护(需显式勾选覆盖)、迁移后立即热切换引擎免重启。同时修复文章批量操作不更新搜索索引的问题,并修复后台用户管理表单字段与业务层脱节导致的多项账号管理异常。无数据库结构变更、无新增 Python 依赖。
Added
- 数据库迁移插件(db_migrator,官方内置,默认不启用)
- 后台「插件管理」启用后出现「数据库迁移」菜单(
db_migrator:manage权限门控,不向预设角色默认授权) - 迁移流程:目标库建表 → 按拓扑序逐表复制(每批 500 行,单事务失败整体回滚)→ PostgreSQL 重置整型主键序列 → 同步源库
alembic_version版本号 - 安全机制:目标库同名表已有数据时拒绝迁移,勾选「覆盖目标库已有数据」才清空重迁;迁移前先测试连通性
- 生效方式:迁移完成自动落盘
instance/db_config.json(重启生效,删除该文件即可放弃);勾选「迁移后立即切换」免重启即时生效(失败自动回滚配置),并清空整站缓存
- 后台「插件管理」启用后出现「数据库迁移」菜单(
- 测试:本版本周期新增 16 个用例,全量 296 项通过
Fixed
- 文章批量操作搜索索引不同步:批量发布/撤回/删除/归档/移动/提交审核此前只清栏目缓存、未逐篇更新索引,导致 Whoosh 等索引型后端与数据库状态不一致
- 后台用户管理表单与业务层脱节:修复「新增账号两次输入密码一致却提示不一致」、新增账号默认禁用、角色未绑定、昵称丢失、重置密码校验失败等问题;非超管无法再通过表单授予超管权限
Notes
- 升级:v3.0.x → v3.1.0 无数据库结构变更、无新增依赖,覆盖代码重启即可,详见 UPGRADE.md
db_migrator插件默认不启用,现有站点升级后行为无任何变化
ZhyCMS v3.0.0 — 可视化页面构建器
v3.0.0 未单独发布发行版,其更新内容随本发行版一并说明。
新增自研轻量拖拽式页面构建器(原生 HTML5 拖拽 + JSON Schema 组件注册 + iframe 内服务端真实渲染),覆盖首页、单页、列表页、文章详情页四种全页面类型;内置 24 个 Bootstrap4 组件(布局 6 + 基础 8 + 数据 10),支持草稿/发布/修订回滚、中英双语文案、语言白名单与会员可见性、桌面/平板/手机三端预览、全局页面样式设置(背景/页脚/标题色/圆角/边框/阴影)。默认布局三层合并:新建构建页按「当前启用主题 fixture → 核心回退」解析基础布局,再叠加已启用插件贡献的布局片段;轮播图/产品/招聘/教程四个内容型插件启用后新首页开箱即用。插件还可通过 get_builder_widgets() 钩子注册自有组件,插件禁用/卸载时前台静默降级、后台卸载前做布局引用拦截。采用「构建器为主、手写模板永久兜底」双轨策略:只有已发布布局的页面才走构建器,老站升级后零行为变化。数据库结构变更:新增迁移 0009(builder_pages / builder_revisions 两张表)。无 Node 构建链、无新增 Python 依赖。
Added
- 可视化页面构建器(核心):四作用域(网站首页/单页/列表页/文章详情页);24 个内置组件;编辑器(组件库、iframe 真实主题画布、属性面板、图层树、拖拽排序与嵌套、三端设备切换与全屏预览、CKEditor5 富文本、800ms 防抖自动存草稿);发布工作流(草稿/已发布双版本隔离、修订快照、一键回滚、从默认布局初始化向导、四分组列表与未构建栏目快捷入口);预览与主题适配(8 套主题全部验证可渲染,组件支持主题级覆写)
- 布局 JSON Schema v1:服务端 allowlist 校验(组件类型/props 白名单/localized 语言键)、最大嵌套深度 8、单页节点上限 300、文档体积上限 256KB;非法草稿/发布返回 400
- 全局页面样式:
layout.styles页面级背景/页脚配色/标题色/圆角/边框/阴影,服务端白名单生成 CSS,预览与生产共用同一规则 - 默认布局三层解析:按当前启用主题解析(主题 fixture → 核心回退);第三方上传主题可自带
builder/<scope>.json,上传时同一套校验,非法整包拒绝;8 套官方主题必备builder/home.json - 插件贡献默认布局片段:
PluginBase.get_builder_layouts()自动扫描builder/<scope>.json,启用强校验、撞 id 自动重写、卸载同步清理;只做一次性快照,已有草稿/已发布页绝不被改写 - 四内容型插件开箱即用:banner 新增
banner-hero首屏轮播组件;product/recruit/tutorial 各新增数据组件与首页区块 - 多语言与可见性:文本类 props 支持
{zh, en}多值并按当前语言回退;节点支持语言白名单与会员限定 - 插件联动:
get_builder_widgets()注册自有组件;插件卸载前若仍有页面引用其组件则拒绝卸载并列出引用页面 - 权限与审计:新增权限点
builder:manage(content_auditor/content_editor)与builder:publish(仅 content_auditor);创建/发布/回滚/删除写入审计日志 - 文档:新增 docs/PAGE_BUILDER.md;wiki.html、SECURITY.md、UPGRADE.md 同步 v3.0.0
- 测试:新增 139 个用例,全量 280 项通过
Changed
- 前台首页/单页/列表页/文章详情页视图接入双轨:存在已发布构建布局时走构建器渲染(缓存键仍含分页/语言/主题),无布局时渲染既有手写模板
- 侧边栏新增「页面构建」一级菜单(受
builder:manage权限门控)
Notes
- 升级:v2.6.x → v3.0.0 需执行迁移
0009(页面构建器两张表),无新增依赖;升级后所有页面默认仍走手写模板,在「页面构建」中创建并发布布局后才切换到构建器 - 不做 header/footer/导航可视化,不提供独立落地页;构建器只接管内容区
- 富文本编辑器复用既有自托管 CKEditor5;图片上传复用
/admin/upload
v2.6.4
v2.6.3(API鉴权升级 + 行业模板扩充):
- REST API 鉴权升级为 JWT(access/refresh 双 token),支持 token/jwt/both 双轨兼容
- 引入 flask-limiter 速率限制(登录5次/分钟、只读GET 120次/分钟)
- 新增教育(亮色调)与餐饮(暖色调)两套行业主题及配套演示数据
v2.6.4(前台会员体系 + 插件依赖治理):
- 新增社区插件 member(前台用户中心):注册/登录/资料/改密/找回密码、
微信/QQ一键登录、短信验证码登录(日志/阿里云/Webhook三通道)、
注册协议管理、栏目会员可见性控制 - 核心新增 Column.member_only 字段(迁移0008)与前台访问守卫钩子,
未登录用户访问会员栏目302跳登录,主导航对游客隐藏会员栏目 - 插件系统新增 requires/extends/min_core_version 强校验:
启用时按「最低核心版本→依赖→父插件」顺序校验,禁用时反向依赖校验 - 后台插件列表展示依赖徽章,上传校验新字段类型
- 8套主题navbar右侧注入会员用户菜单(登录/注册或头像下拉)
v2.5.2
- Whoosh 按语言分索引目录 instance/search_index//
- Meilisearch 按语言分索引 zhycms_articles_
- SQL LIKE 回退适配翻译表匹配
- 每篇文章对每种语言写入索引,内容经 t_field 解析(无翻译回退默认语言)
- 搜索结果标题/摘要/栏目名随当前语言显示
- 文章保存/删除自动同步全部语言索引,重建索引一键生成各语言索引
- 新增 auto_translate 插件:编辑页多语言 Tab 一键调用百度/有道/Google/DeepSeek 翻译回填
- 全站搜索新增 SearchProvider 机制:产品、招聘岗位进入 Whoosh/Meili/SQL 三后端检索,保存/删除/上下架实时同步,索引零命中自动 SQL 兜底;Whoosh 查询补已发布过滤并存储摘要
- 插件结构化内容多语言(迁移 0007):产品规格参数、岗位部门/工作地点/薪资待遇按语言维护,前台/搜索/API 按语言召回,翻译留空回退默认语言
- 修复英文搜索页文案硬编码与核心翻译 .mo 缺失:四个中文主题 search.html 接入 _(),messages.po 补英文词条,启动时自动编译 po→mo
- 产品/招聘插件升级至 1.1.0;README/wiki/CHANGELOG 同步更新
v2.4.2
安全加固 + 部署修复版本
针对 v2.4.1 安全复检报告的遗留建议进行纵深防御加固,并修复 Docker 部署链路中的多项问题。无功能变更、无数据库结构变更,覆盖代码即可升级。
安全加固
- CSRF 纵深防御(Origin/Referer 同源校验):所有 Cookie 鉴权的状态变更请求必须携带与本站一致的
Origin/Referer头,跨站来源直接 403;REST API 与非浏览器客户端豁免。 - 纯文本字段移除
|safe:6 套主题及表单/招聘插件的 19 处纯文本字段改为自动转义,封堵存储型 XSS。
部署修复
- Docker worker 启动失败:
requirements.txt新增cryptography(PyMySQL 连 MySQL 8+ 必需);entrypoint.sh以 root 修正挂载目录属主后经gosu降权,并增加数据库就绪等待。 - CSP 拦截 CDN 静态资源:AdminLTE、FontAwesome、jQuery、Bootstrap、icheck-bootstrap 全部本地化到
app/static/vendor/,CSP 保持严格'self'。 - 初始化向导填 localhost 连库失败:检测到
ZHYCMS_DB_URI环境变量时隐藏数据库配置区,直接使用已连通的引擎。 - 自定义数据库密码不生效:install.sh 检测已有数据卷时提供「沿用旧密码 / 清空重建」选择,启动后校验凭据。
新增
- Docker 一键安装/卸载脚本:交互式配置 + 自动构建启动;compose 改用 YAML 锚点兼容 v2,支持自定义端口。
升级说明
- v2.4.0/v2.4.1 站点:覆盖代码即可,无需数据库迁移
- Docker 部署:
git pull后重新bash docker/install.sh(或docker compose up -d --build)
v2.4.1
Alembic 迁移 + 全文搜索 + Docker 容器化 + 对象存储 OSS版本。v2.3.x 覆盖代码升级后首次启动自动 stamp baseline + 执行增量迁移,无需手动操作;全文搜索默认使用 Whoosh + jieba 中文分词,SQL LIKE 自动回退;Docker 支持 MySQL / PostgreSQL 一键部署;新增官方内置 oss_storage 插件,支持阿里云 OSS / 腾讯云 COS / 七牛云 Kodo 云端对象存储与本地存储一键切换(默认本地,零行为变化)。