⚙️ 后台扩展与运维
最后更新: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/audit | JSON 列表: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 S3 | Access Key ID / Secret / Bucket / Region | s3.<region>.amazonaws.com |
| Cloudflare R2 | 账户 ID / Access Key / Secret / Bucket | <account>.r2.cloudflarestorage.com |
| Google Cloud Storage | HMAC 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 / Naver | indexnow_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_push | API 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,回收空间、优化索引 |
Gitl.cn云建站 SaaS