⚠ 平台测试中。

⚙️ 后台扩展与运维

最后更新:2026-10-02

🔔 外部事件 Webhook

内容生命周期事件(创建 / 更新 / 删除)会通过事件总线广播,可推送到外部系统(如搜索索引、CDN 刷新、企业微信机器人)。配置以下环境变量后自动启用,未配置时为零开销空操作。

📍 环境变量
变量说明
CMS_WEBHOOK_URLS目标地址,逗号分隔,如 https://a.com/hook,https://b.com/x
CMS_WEBHOOK_EVENTS订阅事件白名单,逗号分隔;缺省或含 * 表示接收全部

事件类型:content.created / content.updated / content.deleted / system.media.deleted / system.backup.restored。其中系统级事件(媒体删除、备份恢复)统一带 system. 前缀——CMS_WEBHOOK_EVENTS 必须写完整前缀(如 system.media.deleted)才能匹配,只写 media.deleted 会永远收不到。推送为异步 POST + 自动重试,Body 示例:

POST {CMS_WEBHOOK_URLS 中的地址}
Content-Type: application/json

{
  "type": "content.created",
  "payload": {
    "content_type": "post",   // post / product / case / page
    "content_id": 11896,
    "site_id": 1,
    "actor": "admin",
    "ip": "127.0.0.1",
    "timestamp": "2026-08-24T17:41:02+08:00"
  }
}

🧩 扩展点 / 钩子总线

系统内置进程内事件总线(internal/hooks),Webhook 即基于此实现。开发者可注册自定义钩子或自定义路由,无需改动核心代码。

// 广播一条内容事件(已在各内容保存/删除处理器中调用)
hooks.FireContentEvent(action, contentType string, id, siteID int, actor, ip string)
//  action: "created" | "updated" | "deleted" | "published"

// 注册自定义钩子(接收全部事件)
hooks.RegisterHook(func(evt hooks.Event) { /* 自定义逻辑 */ })

// 注册自定义路由(在启动期 ApplyRouteRegistrars 时挂载)
hooks.RegisterRouteRegistrar(func(r *gin.Engine) {
    r.GET("/my-plugin", myHandler)
})

自定义模板函数:在 GetTemplateFuncs 中注册后,前台模板即可使用;注意函数需在模板函数表内预先声明,不可在模板里直接调用未注册的函数名。

🖼️ 媒体库

📍 菜单与接口
入口说明
后台 → 媒体库(/admin/media)列出 static/uploads/ 下全部文件(图片自动识别 is_image、扩展名 ext)
GET /admin/media/api返回 JSON 文件清单(files: [{path, size, ext, is_image, modified}])
POST /admin/media/delete表单字段 path=/static/uploads/xxx,删除指定文件并记录审计

⚠️ 删除仅移除文件,不会自动解除文章里对该图的引用;删除前请确认无正文引用。

💾 备份与恢复

一键导出 / 导入全部内容(内置表 + 自定义模型表),JSON 格式、方言安全(SQLite / MySQL / PostgreSQL 均可还原)。

📍 入口
入口说明
后台 → 备份与恢复(/admin/backup)页面提供导出 / 导入按钮
GET /admin/backup/export下载 cms-backup-<时间戳>.json(内容数据 JSON)
GET /admin/backup/sqlite仅 SQLite 部署可用:下载整库物理文件 cms-sqlite-backup-<时间戳>.db
POST /admin/backup/restore表单字段 file 上传 JSON 进行还原(逐表 upsert)

🗄️ SQLite 整库备份:当前数据库方言为 SQLite 时,备份页额外提供「下载备份(.db 整库)」按钮(接口 GET /admin/backup/sqlite?download=1)。它直接对 cms.db 做瞬时一致性快照(先执行 VACUUM INTO 生成临时副本,再流式下载并删除临时文件),得到的是一份与线上完全一致的、可直接被程序重新打开的 SQLite 物理文件——区别于上面的「内容数据 JSON」。

  • 适用场景:整机 / 文件级迁移、需要连同 settings/用户/审计等全部表一起备份、或想用数据库工具直接打开查看。
  • 非 SQLite 环境(MySQL / PostgreSQL):该按钮不显示,接口直接返回 400,请用上面的 JSON 备份。
  • 范围说明:与 JSON 备份一样,均不含 uploads/ 下的真实媒体文件,媒体文件需另行用文件系统 / 镜像级方式备份。

导出结构:{ version, exported_at, dialect, tables: { 表名: [ 行对象... ] } },每行字段统一为字符串,便于跨方言还原。还原会按主键 / 唯一键更新已有记录。

📏 备份页会显示「当前备份数据大小」(如 1.23 MB),这是即将导出的 JSON 内容体体积——即全部内容表记录序列化后的字节数,作为数据量估算。它不是磁盘上 cms.db 文件的大小,也不含上传到 uploads/ 的真实图片 / 视频等媒体文件(仅含其数据库记录)。

📝 审计日志

记录关键操作(登录、内容增删改、媒体删除、备份恢复),供安全追溯。

📍 入口
入口说明
后台 → 审计日志(/admin/audit)审计列表页
GET /admin/api/auditJSON 列表:logs: [{id, action, actor, target_type, target_id, detail, ip, site_id, created_at}]

已记录动作示例:auth.login / auth.login_fail / content.created / content.updated / content.deleted / media.deleted / backup.restored。注意审计动作本身不带 system. 前缀(该前缀仅出现在 Webhook 推送的 event type 上)。代码中可用 RecordAudit(siteID, actor, action, targetType, targetID, detail, ip) 或便捷方法 RecordContentAudit(c, action, ct, id) 追加记录。

📥 批量增加 / 删除

后台对 文章 / 产品 / 案例 / 单页 / 栏目 五类内容提供批量操作:列表页可勾选多条并一次性删除,并提供独立的「批量添加」页一次性创建多条。所有批量删除均带站点隔离(仅删除当前站点数据),与单条删除行为一致。

📍 入口与接口
入口说明
各内容列表页(文章 / 产品 / 案例 / 单页 / 栏目)每行加勾选框;列表顶部含「批量添加」按钮与「批量删除选中」按钮(带全选、已选计数、二次确认)
GET /admin/{type}/batch批量添加页({type} ∈ posts / products / cases / pages / categories;categories 对应「栏目」)
POST /admin/{type}/batch-save批量保存:表单字段为数组(如 title[] / name[]),同一栏目、同一状态下批量创建;空标题 / 空名称的行自动跳过
POST /admin/{type}/batch-delete批量删除:表单字段 ids(多选),一次性删除;不可恢复,提交前需确认

批量添加页支持两种录入方式:① 在表格里逐行填写(标题 / 名称、摘要、所属栏目、价格、状态等);② 用「快速导入」文本框每行粘贴一个标题 / 名称后一键转为行(产品 / 案例 / 栏目还可指定「导入到栏目 / 上级栏目」)。栏目的 URL 标识、单页的别名(slug)留空时,系统会自动用记录编号填充。

📄 新增后台模板 partial:templates/admin/partials/posts_batch.html / products_batch.html / cases_batch.html / pages_batch.html / categories_batch.html。列表页 posts_list.html / products_list.html / cases_list.html / pages_list.html(栏目为 AdminCategories 内联渲染)已分别加入勾选框与批量删除表单。

  • 站点隔离:批量删除走 repo.Delete(id, siteID)(文章 / 产品 / 案例 / 栏目)或 DELETE ... WHERE id=? AND site_id=?(单页),仅影响当前后台站点。
  • 不级联:批量删除栏目时,不会自动级联删除其子栏目或内容(与单条删除一致),请先处理好子项再删除。
  • 不可恢复:批量删除为硬删除,操作前请确认勾选项,必要时先备份。

🌐 翻译状态核查

对比源站点与目标站点同一内容表,列出「目标站点缺失」或「目标标题为空」的待翻译项。

入口:后台 → 翻译状态核查(/admin/i18n)。查询参数:type(posts / products / cases / pages)、src(源站点 ID)、dst(目标站点 ID)。页面展示源 / 目标条数与待翻译清单(含内容 ID、Code/Slug、标题、缺失原因)。

📦 对象存储发布(S3 / R2 / GCS / Azure)

静态站发布新增四种对象存储目标,与原有 FTP / EdgeOne / Git / 腾讯云 COS 并列。客户可在后台自由开启任意一个或多个:每种独立配置、独立「仅 XXX」发布按钮,并纳入「全部发布」。上传方式均把 htm/ 目录以 PUT 推送到指定 Bucket / 容器,对象键 = 「远程目录」前缀 + 文件相对路径,同名对象覆盖。

🔧 实现说明:纯标准库 HTTP + 手写签名,零外部 SDK 依赖。Amazon S3 / Cloudflare R2 / Google Cloud Storage 走 S3 兼容 XML API,共用同一套 AWS SigV4 签名(R2 / GCS 的签名 region 用 auto);Azure Blob Storage 走 Shared Key 签名。代码见 internal/handlers/objectstorage.go。

📍 各厂商配置项(后台 → 发布)
厂商关键配置默认 Endpoint(留空时)
Amazon S3Access Key ID / Secret / Bucket / Regions3.<region>.amazonaws.com
Cloudflare R2账户 ID / Access Key / Secret / Bucket<account>.r2.cloudflarestorage.com
Google Cloud StorageHMAC Access Key / Secret / Bucket(需在 GCS「互操作」页创建 HMAC 密钥)storage.googleapis.com
Azure Blob Storage存储账户 / 访问密钥(Base64) / 容器<account>.blob.core.windows.net

每个厂商均支持可选自定义 Endpoint——既兼容各类 S3 兼容服务,也便于对接私有部署。路由:POST /admin/deploy/s3 / /r2 / /gcs / /azure,统一发布 POST /admin/deploy/run。

🎛️ 配置参数(后台 → 配置)

新增的统一配置入口(/admin/config),把原先散落在各处的站点级开关集中成 8 个 tab,全部可保存可改。除「基本配置」直接写 sites 表外,其余统一存 settings 表(key/value);保存接口 POST /admin/config/save(按 tab 分组落库)。

📍 8 个 tab 与对应设置项
tab说明主要设置项(settings key)
基本配置站点标题 / 域名 / Logo / 备案 / 统计 / 模板目录等,复用 /admin/site/save—(写 sites 表)
百度接口百度站长平台主动推送(普通收录)baidu_enabled / baidu_site / baidu_token / baidu_auto_push
IndexNow推送一次提交同时触达 Bing / Yandex / Seznam / Naverindexnow_enabled / indexnow_key / indexnow_endpoint / indexnow_key_location / indexnow_auto_push
WebAPI对外只读 JSON 接口(详见下节)webapi_enabled / webapi_token / webapi_allow_ip / webapi_limit
安全配置登录失败锁定 / 后台 IP 白名单 / 会话策略 / 上传黑名单sec_login_fail_limit / sec_login_lock_minutes / sec_idle_timeout / sec_session_ip_bind / sec_admin_ip_whitelist / sec_upload_ext_blacklist
URL规则详情页后缀、命名(ID / 别名)、分页形式——影响前台所有 url_detail_suffix / url_detail_name / url_page_style
标题样式标题分隔符与顺序、首页标题模板、自动摘要长度——影响前台 seo_title_sep / seo_title_order / seo_home_title / seo_desc_len
远程附件配置复用既有远程附件表单与保存接口同「远程附件」原设置

⚠️ 「URL规则」改动后需重新生成静态站才会对 HTML 落地文件生效(动态页即时生效);seo_desc_len 自动摘要长度影响未手动填摘要内容的默认截取长度(列表 / )。

🌐 WebAPI(对外只读 JSON 接口)

后台内置的对外只读接口(仅 GET,只返回 status=1 的已发布内容),已取代旧版 /api/v1/posts 系列接口。开关与令牌在 后台 → 配置参数 → WebAPI 设置;全部只读,令牌泄露也不会造成写操作。

📍 端点
方法路径说明
GET/api/v1/list?type=news&num=10&page=1&site=1&token=xxx内容列表
GET/api/v1/detail?type=news&id=13&site=1&token=xxx内容详情(含正文)
GET/api/v1/categories?site=1&token=xxx栏目列表
# 列表
GET /api/v1/list?type=news&num=10&page=1&site=1&token=xxx
# type: news(posts) / products / cases / jobs / pages
# num: 1-100,默认 10;page: 默认 1;site: 默认当前默认站点
# 返回:
{ "ok": true, "type": "news", "total": 212, "page": 1, "num": 10,
  "items": [ { "id":13, "slug":"", "title":"...", "summary":"...",
               "image":"...", "url":"/news/13", "seo_title":"", "keywords":"",
               "description":"", "created_at":"2026-08-03T..." } ] }

# 详情
GET /api/v1/detail?type=news&id=13&token=xxx
# 返回 {"ok":true,"item":{ ...同上字段 + "content":"正文HTML" }}

# 栏目
GET /api/v1/categories?token=xxx
# 返回 {"ok":true,"total":8,"items":[{ "id":2,"name":"产品","link":"products",
#          "url":"/category/products","description":"" }]}

字段说明:url 已按后台「URL规则」拼好(含站点前缀与后缀,如 /en/news/13.html),前端可直接用;jobs 表无 slug 列,其 slug 恒为空、URL 固定用 ID;title 对产品取 name 列。

🔐 鉴权与拦截(由 APIAuth 中间件统一管理,顺序:开关 → IP 白名单 → 令牌 → 限流):
· 开关:webapi_enabled≠1 时所有请求返回 404 {"ok":false,"error":"接口未开启"}。
· IP 白名单:webapi_allow_ip 非空时,非白名单 IP 返回 403(支持单 IP 与 CIDR,逗号 / 换行分隔)。
· 令牌:webapi_token 非空时,调用方需在查询参数 ?token= 或请求头 X-API-Token 携带,错误返回 401;令牌为空表示不校验(不推荐)。可在配置页点「生成令牌」调用 GET /admin/config/api-token 获得 32 位随机串。
· 限流:webapi_limit 为每 IP 每分钟上限(默认 60),超出返回 429。

手动推送 / 生成令牌等写操作走后台会话鉴权(管理员登录),不对外暴露。

🔍 搜索引擎主动推送(百度接口 / IndexNow)

在 后台 → 配置参数 的「百度接口」「IndexNow推送」两个 tab 配置后,可把站点链接主动提交给搜索引擎,加速收录。前提是站点信息里已填写域名(否则拼不出绝对 URL)。

📍 配置项
来源设置项说明
百度接口baidu_enabled / baidu_site / baidu_token / baidu_auto_push站点(域名)、准入密钥(百度站长平台获取);开启后向 data.zz.baidu.com/urls 推送
IndexNow推送indexnow_enabled / indexnow_key / indexnow_endpoint / indexnow_key_location / indexnow_auto_pushAPI Key(Bing Webmaster 获取,密钥文件放站点根目录 <key>.txt);默认端点 api.indexnow.org/IndexNow(一次提交触达 Bing / Yandex / Seznam / Naver)
  • 手动推送:配置页点「立即推送」,调用 POST /admin/config/push(表单 target=baidu|indexnow、limit=200)。提交范围 = 首页 + 三大列表页 + 栏目页 + 最近详情(默认 200 条)。返回 {ok,msg,count}。
  • 保存后自动推送:开启对应 _auto_push 后,每次保存 / 发布内容会异步提交该条 URL;失败仅记日志,绝不阻断内容保存主流程。

返回码含义:百度 HTTP 200 & error=0 为成功;IndexNow 200 已接收、202 已排队(Key 未验证也会返回)、429 限流。

🛡️ 安全配置

在 后台 → 配置参数 → 安全配置 统一设置后台与上传安全策略,全部缺省即保持历史行为(不限制)。

📍 设置项
设置项说明默认
sec_login_fail_limit连续登录失败达到该次数后锁定账号(0 = 不限制)5
sec_login_lock_minutes触发上限后的锁定时长(分钟)15
sec_idle_timeout后台闲置超时(分钟),超时需重新登录30
sec_session_ip_bind会话 IP 绑定:登录后客户端 IP 变化即失效(防会话劫持)开启
sec_admin_ip_whitelist后台访问 IP 白名单(单 IP / CIDR,逗号或换行分隔);接入认证中间件,非空时非白名单 IP 无法进入后台不限制
sec_upload_ext_blacklist上传扩展名黑名单(不含点、小写),命中则拒绝上传不限制

读取出口集中在 internal/handlers/security.go,由 middleware 包(认证、登录限流)与 WebAPI 统一调用;后台 IP 白名单与 WebAPI 的 webapi_allow_ip 是两套独立配置,分别守护后台与对外接口。

🛠️ 系统更新(系统信息与维护)

新增的运维页(后台 → 系统设置 → 系统更新,/admin/system-update),展示运行时信息与常用维护操作。

📍 展示信息
项说明
程序版本 / 构建时间编译期注入的 version / buildTime
Go 版本运行时 runtime.Version()
数据库大小cms.db 文件体积
运行时长进程启动至今
静态站大小静态导出目录体积
📍 维护操作(POST,需管理员登录)
操作接口说明
清理运行日志POST /system/clear-logs清空 logs/ 下日志文件
整理数据库POST /system/optimize-db对 SQLite 执行 VACUUM,回收空间、优化索引
本网站使用 Cookie 以提升浏览体验,继续浏览即表示您同意我们的 Cookie 政策。