fix anwser token and database issus
This commit is contained in:
+8
-13
@@ -12,7 +12,7 @@ import (
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// File is the configuration object.
|
||||
// File 是解析后的配置对象。
|
||||
var File *ini.File
|
||||
|
||||
func Init() error {
|
||||
@@ -110,19 +110,14 @@ func Init() error {
|
||||
}
|
||||
|
||||
// validateTextCensorSource 校验文本审核通道([security] text_censor_source)与凭据的搭配。
|
||||
// 抽成纯函数(只依赖入参、不读全局)以便单测覆盖,也让 Init 保持轻薄。
|
||||
//
|
||||
// 单独抽成纯函数(只依赖入参、不读全局)是为了能直接被单测覆盖,也让 Init 保持轻薄。
|
||||
//
|
||||
// 规则:
|
||||
// - 审核整体关闭(enabled=false)→ 不校验,此时不需要任何凭据。
|
||||
// - 留空 / auto → 通过。auto 是历史默认(七牛优先、阿里云兜底),两者都没配凭据时
|
||||
// 维持既有行为(运行期 fail-closed),不阻断启动 —— 否则 app.sample.ini 原样拷贝
|
||||
// (enable_text_censor = true 且凭据留空)将无法启动,破坏开箱流程。
|
||||
// - 显式选 qiniu / aliyun → 必须配齐该通道的凭据,否则启动即失败。
|
||||
// 理由:缺凭据时运行期审核必然失败,而审核失败是 fail-closed(拒绝提交),
|
||||
// 会让发布/私信整站不可用,且现象隐蔽(只在日志可见)——不如启动就报错。
|
||||
// - 其它取值 → 直接报错,不做静默回退:拼写错误若被悄悄当成 auto,
|
||||
// 运维会以为「已经只用七牛了」,实际仍在走双通道。
|
||||
// - 审核整体关闭(enabled=false)→ 不校验,不需要任何凭据。
|
||||
// - 留空 / auto → 通过。auto 是历史默认(七牛优先、阿里云兜底),两者都无凭据时
|
||||
// 维持既有运行期 fail-closed 行为,不阻断启动(否则 app.sample.ini 原样拷贝无法启动)。
|
||||
// - 显式选 qiniu / aliyun → 必须配齐该通道凭据,否则启动即失败:缺凭据时运行期审核必失败,
|
||||
// 而审核失败是 fail-closed(拒绝提交),会让发布/私信整站不可用且现象隐蔽。
|
||||
// - 其它取值 → 直接报错,不做静默回退:拼写错误若被当成 auto,运维会误以为已切通道。
|
||||
func validateTextCensorSource(enabled bool, source, qiniuKey, qiniuSecret, aliyunKey, aliyunSecret string) error {
|
||||
if !enabled {
|
||||
return nil
|
||||
|
||||
+21
-45
@@ -23,62 +23,39 @@ const (
|
||||
|
||||
// LimitsConfig 全站内容 / 数量上限([limits] 配置段)的字段集合。
|
||||
//
|
||||
// 过去这些上限以常量形式散落在 internal/db(MaxTags / MaxCustomTextLen 等)与
|
||||
// 路由层硬编码(私聊单条 2000 字、会话条数 clamp 5~200 等)中,调整需改代码重编译,
|
||||
// 且前后端、i18n 文案各自携带一份数值容易漂移。统一收口到 [limits] 段后:
|
||||
// - 后端校验、模板注入、前端提示全部引用 conf.Limits 的同一份值;
|
||||
// - 调整仅需修改 conf/app.ini 并重启进程,无需改代码;
|
||||
// - 长度类上限统一按字符(rune / Unicode 码点)计数。
|
||||
// 收口到本段前,这些上限散落在 internal/db 与路由层硬编码,改一次要重编译且前后端数值易漂移。
|
||||
// 现后端校验 / 模板注入 / 前端提示统一引用同一份值,调整只需改 app.ini 重启;
|
||||
// 长度类上限一律按字符(rune)计数。
|
||||
type LimitsConfig struct {
|
||||
// ── 标签与屏蔽词(账号设置页)──
|
||||
|
||||
// 标签个数上限
|
||||
MaxTags int `ini:"max_tags"`
|
||||
// 单个标签最大字符数
|
||||
MaxTagLen int `ini:"max_tag_len"`
|
||||
// 屏蔽词个数上限
|
||||
MaxBlockWords int `ini:"max_block_words"`
|
||||
// 单个屏蔽词最大字符数
|
||||
MaxTags int `ini:"max_tags"`
|
||||
MaxTagLen int `ini:"max_tag_len"`
|
||||
MaxBlockWords int `ini:"max_block_words"`
|
||||
MaxBlockWordLen int `ini:"max_block_word_len"`
|
||||
|
||||
// ── 投票 ──
|
||||
|
||||
// 参与者填写的「自定义补充选项」文本最大字符数。
|
||||
// 覆盖两类入口:单选/多选/排序的全局补充选项,以及表单收集字段的
|
||||
// 「其他/自定义选项」(field_{id}_custom)。
|
||||
// 参与者填写的「自定义补充选项」文本最大字符数(含表单字段的「其他」)。
|
||||
CustomAnswerLen int `ini:"custom_answer_len"`
|
||||
// 站长创建投票时单个「选项」文本的最大字符数。
|
||||
// 覆盖单选/多选/排序的预设选项,以及表单收集字段(select/radio/checkbox/rank)
|
||||
// 的预置选项。与 CustomAnswerLen 是两条独立规则(站长侧预设刻意放宽),可各自调参。
|
||||
// 站长创建投票时单个「选项」文本最大字符数。与 CustomAnswerLen 是两条独立规则
|
||||
// (站长侧预设刻意放宽),可各自调参。
|
||||
VoteOptionLen int `ini:"vote_option_len"`
|
||||
|
||||
// ── 提问 / 回答 / 追问 ──
|
||||
|
||||
// 提问正文最大字符数(访客向提问箱提交的问题内容)。
|
||||
// 旧实现依赖 govalid 的 maxlen 标签(编译期写死 2000),无法随配置调整,
|
||||
// 且与前端 maxlength=1000 不一致,现已移到路由层按本值做 rune 校验。
|
||||
// 提问正文最大字符数。旧实现靠 govalid maxlen 写死 2000 且与前端 1000 不一致,
|
||||
// 现改为路由层按本值做 rune 校验。
|
||||
QuestionContentLen int `ini:"question_content_len"`
|
||||
// 回答正文最大字符数:箱主对提问的正式回答,以及箱主对追问的回复(共用)。
|
||||
AnswerContentLen int `ini:"answer_content_len"`
|
||||
// 追问正文最大字符数(提问者在问题被回答后继续追问的内容)。
|
||||
// 回答正文最大字符数:箱主对提问的正式回答,以及对追问的回复(共用)。
|
||||
AnswerContentLen int `ini:"answer_content_len"`
|
||||
FollowUpContentLen int `ini:"followup_content_len"`
|
||||
|
||||
// ── 私聊 ──
|
||||
|
||||
// 单条消息(发送 / 回复共用)最大字符数
|
||||
ChatMessageLen int `ini:"chat_message_len"`
|
||||
// 每个私聊会话的消息条数上限(系统硬顶)。用户只能在 1..该值 之间设置自己的
|
||||
// chat_max_messages;新注册用户的默认值即该硬顶(而非固定 20)。
|
||||
// 每个会话的消息条数硬顶。用户只能在 1..该值间设置 chat_max_messages,
|
||||
// 新用户默认值即该硬顶(而非固定 20)。
|
||||
ChatMaxMessages int `ini:"chat_max_messages"`
|
||||
// 单用户每日私信条数上限(用户可设范围 0..该值;0 表示不限制每日条数)。
|
||||
// 该值只是「用户可设置的上限」,不是站点强制的每日配额。
|
||||
// 每日私信条数的「用户可设上限」(非站点强制配额):用户可设 0..该值,0 表示不限制。
|
||||
DailyMessageLimitMax int `ini:"daily_message_limit_max"`
|
||||
}
|
||||
|
||||
// Limits 保存运行时的全部上限配置。conf.Init 中会先 MapTo [limits] 段覆盖显式键,
|
||||
// 再调用 sanitizeLimits 把 <1 的值回退到内置默认,因此:
|
||||
// - 缺失 [limits] 段或个别键 → 使用内置默认;
|
||||
// - ini 键留空(int 解析为 0)或误填 0/负数 → 自动回退默认,不会把上限静默置零。
|
||||
// Limits 保存运行时的全部上限配置。conf.Init 先 MapTo [limits] 段,再由 sanitizeLimits
|
||||
// 把 <1 的值回退内置默认,因此缺段、缺键、留空(解析为 0)或填负数都不会让上限静默置零。
|
||||
var Limits = LimitsConfig{
|
||||
MaxTags: defaultMaxTags,
|
||||
MaxTagLen: defaultMaxTagLen,
|
||||
@@ -94,10 +71,9 @@ var Limits = LimitsConfig{
|
||||
DailyMessageLimitMax: defaultDailyMessageLimitMax,
|
||||
}
|
||||
|
||||
// sanitizeLimits 归一化 [limits] 配置:所有上限最小为 1(0 / 负数说明未配置或手滑,
|
||||
// 一律回退内置默认,避免「留空=0」把标签全拒、消息全超长等隐性灾难)。
|
||||
// 注意 DailyMessageLimitMax 同样要求 >=1:0 会让用户侧永远只能设为 0(=不限制),
|
||||
// 语义上等于关闭每日配额功能,需显式使用该字段其余语义时请另行设计。
|
||||
// sanitizeLimits 归一化 [limits]:所有上限最小为 1,0 / 负数一律回退内置默认,
|
||||
// 避免「留空=0」把标签全拒、消息全超长等隐性灾难。
|
||||
// DailyMessageLimitMax 同样要求 >=1:为 0 会让用户只能设为 0(=不限制),等于关闭每日配额功能。
|
||||
func sanitizeLimits(l *LimitsConfig) {
|
||||
clamp1 := func(p *int, def int) {
|
||||
if *p < 1 {
|
||||
|
||||
+29
-50
@@ -4,24 +4,17 @@
|
||||
|
||||
package conf
|
||||
|
||||
// Build time and commit information.
|
||||
//
|
||||
// ⚠️ WARNING: should only be set by "-ldflags".
|
||||
// ⚠️ 仅应由 "-ldflags" 注入。
|
||||
var (
|
||||
BuildTime string
|
||||
BuildCommit = "dev"
|
||||
)
|
||||
|
||||
// 文本审核服务通道取值([security] text_censor_source)。
|
||||
//
|
||||
// 定义在 conf 侧是为了让「配置校验」(conf.Init)与「通道装配」
|
||||
// (internal/security/censor)引用同一份字面量,避免两边各写一份字符串而漂移。
|
||||
// 文本审核通道取值([security] text_censor_source)。定义在 conf 侧,
|
||||
// 让配置校验(conf.Init)与通道装配(internal/security/censor)共用同一份字面量。
|
||||
const (
|
||||
// CensorSourceAuto 七牛优先、阿里云兜底(历史行为,留空同义)。
|
||||
CensorSourceAuto = "auto"
|
||||
// CensorSourceQiniu 只用七牛。
|
||||
CensorSourceQiniu = "qiniu"
|
||||
// CensorSourceAliyun 只用阿里云。
|
||||
CensorSourceAuto = "auto" // 七牛优先、阿里云兜底(历史行为,留空同义)
|
||||
CensorSourceQiniu = "qiniu"
|
||||
CensorSourceAliyun = "aliyun"
|
||||
)
|
||||
|
||||
@@ -47,43 +40,32 @@ var (
|
||||
IPBlacklist string `ini:"ip_blacklist"`
|
||||
IPBlacklistRedirectURL string `ini:"ip_blacklist_redirect_url"`
|
||||
// TrustProxy 为 true 时信任 CDN 权威客户端头 CF-Connecting-IP / EO-Client-IP
|
||||
// (以及受信代理设置的 X-Real-IP / X-Forwarded-For);否则一律使用 RemoteAddr,
|
||||
// 防止客户端伪造 header 绕过速率限制与 IP 黑名单。
|
||||
// (以及受信代理设置的 X-Real-IP / X-Forwarded-For);否则一律用 RemoteAddr,
|
||||
// 防止伪造 header 绕过速率限制与 IP 黑名单。
|
||||
TrustProxy bool `ini:"trust_proxy"`
|
||||
// TrustedProxies 逗号分隔的 IP / CIDR 列表,仅限“你自己的”反向代理/负载均衡基础设施
|
||||
// (例如本机 nginx 的 127.0.0.1、私有网络 LB)。仅当 TCP 对端落在该清单内时才会解析
|
||||
// X-Forwarded-For / X-Real-IP。留空则这两个头永不被信任(CDN 权威头 CF-Connecting-IP /
|
||||
// EO-Client-IP 仍会被采用,前提是源站已被对应 CDN 锁定)。
|
||||
// 目的:避免维护 Cloudflare / EdgeOne 等上游 CDN 的庞大 CIDR 清单——源站应配合
|
||||
// Cloudflare Tunnel / Authenticated Origin Pull / 防火墙仅放行 CDN 回源网段锁定,
|
||||
// 使 CF-Connecting-IP / EO-Client-IP 成为权威客户端 IP。
|
||||
// TrustedProxies 逗号分隔的 IP / CIDR,仅限自己的反向代理 / LB。仅当 TCP 对端
|
||||
// 落在该清单内才解析 X-Forwarded-For / X-Real-IP;留空则这两个头永不被信任
|
||||
// (CDN 权威头仍会采用,前提是源站已由 Cloudflare Tunnel / 回源网段锁定,
|
||||
// 从而免于维护上游 CDN 的庞大 CIDR 清单)。
|
||||
TrustedProxies string `ini:"trusted_proxies"`
|
||||
// 自动封禁(fail2ban 风格):检测批量扫描/探测特征后临时封禁来源 IP。
|
||||
EnableAutoBan bool `ini:"enable_auto_ban"`
|
||||
// BanTrackNotFound 是否把「未注册路径的 404」计入封禁统计。默认 false:正常业务
|
||||
// (域名/题目不存在等均走 302 业务跳转,不产生计数)一律放行,仅命中后台扫描特征
|
||||
// 路径(KindSensitive)才参与统计封禁。避免共享出口(CDN 边缘 / CGNAT NAT444)下
|
||||
// 正常用户被 404 探测流量连坐误伤。如需恢复对纯 404 字典扫描的封禁再置 true。
|
||||
EnableAutoBan bool `ini:"enable_auto_ban"` // fail2ban 风格:检测到扫描特征后临时封禁来源 IP
|
||||
// BanTrackNotFound 是否把未注册路径的 404 计入封禁统计。默认 false:仅命中后台
|
||||
// 扫描特征路径(KindSensitive)才统计,避免共享出口(CDN 边缘 / CGNAT)下
|
||||
// 正常用户被 404 探测流量连坐误伤。
|
||||
BanTrackNotFound bool `ini:"ban_track_notfound"`
|
||||
BanThresholdSensitive int `ini:"ban_threshold_sensitive"` // 窗口内敏感路径命中次数
|
||||
BanThreshold404 int `ini:"ban_threshold_404"` // 窗口内 404 次数
|
||||
BanWindowSeconds int `ini:"ban_window_seconds"` // 统计窗口(秒)
|
||||
BanDurationMinutes int `ini:"ban_duration_minutes"` // 封禁时长(分钟)
|
||||
// DefaultModerationStrictness 站点默认的「内容审核严格度」档位
|
||||
// (lax / standard / strict)。仅对未单独设置档位的账号生效:
|
||||
// 账号一旦显式选择了档位就以账号设置为准(见 moderation.StrictnessFor)。
|
||||
// 留空或非法值回退 strict(最严档)—— 保证升级不会放宽任何拦截;
|
||||
// 严格档拦截面不小于升级前,部署后默认拦截面可能略有扩大;
|
||||
// 若希望默认更少误伤,在本段显式写 standard 即可,无需改代码。
|
||||
// DefaultModerationStrictness 站点默认审核严格度(lax / standard / strict),
|
||||
// 仅对未单独设置档位的账号生效(见 moderation.StrictnessFor)。留空或非法值回退
|
||||
// strict(最严档),保证升级不会放宽拦截;默认拦截面可能较升级前略有扩大,
|
||||
// 希望更少误伤则显式写 standard。
|
||||
DefaultModerationStrictness string `ini:"default_moderation_strictness"`
|
||||
// TextCensorSource 文本审核服务通道,取值:
|
||||
// qiniu —— 只用七牛(凭据见 [app] qiniu_access_key / qiniu_access_secret)
|
||||
// aliyun —— 只用阿里云(走内容安全 2.0 / 文本审核增强版 TextModeration;
|
||||
// 开通只需实名认证,个人账号可用。凭据见 [app] aliyun_access_key / aliyun_access_key_secret)
|
||||
// auto(默认,留空同义)—— 保持历史行为:七牛优先,未通过时用阿里云二次复核
|
||||
// 仅显式选定的通道会被调用;选定通道缺少凭据时启动即报错(见 conf.Init),
|
||||
// 避免运行时审核必然失败 —— 审核失败是 fail-closed(拒绝提交),
|
||||
// 会让发布/私信整站不可用,且现象隐蔽(只在日志里)。
|
||||
// TextCensorSource 文本审核通道:qiniu / aliyun(内容安全 2.0 TextModeration,
|
||||
// 个人实名账号即可开通)/ auto(默认,留空同义:七牛优先、阿里云复核)。
|
||||
// 仅显式选定的通道会被调用,缺凭据时启动即报错(见 conf.Init)——
|
||||
// 审核失败是 fail-closed(拒绝提交),会让发布与私信整站不可用且现象隐蔽。
|
||||
TextCensorSource string `ini:"text_censor_source"`
|
||||
SecurityAlertEmail string `ini:"security_alert_email"` // 触发封禁时告警收件人(留空不发送)
|
||||
}
|
||||
@@ -142,12 +124,11 @@ var (
|
||||
}
|
||||
|
||||
Mail struct {
|
||||
Account string `ini:"account"`
|
||||
Password string `ini:"password"`
|
||||
Port int `ini:"port"`
|
||||
SMTP string `ini:"smtp"`
|
||||
// SkipTLSVerify 跳过 SMTP TLS 证书校验(仅自签名/内网 SMTP 需要,默认 false 校验证书)
|
||||
SkipTLSVerify bool `ini:"skip_tls_verify"`
|
||||
Account string `ini:"account"`
|
||||
Password string `ini:"password"`
|
||||
Port int `ini:"port"`
|
||||
SMTP string `ini:"smtp"`
|
||||
SkipTLSVerify bool `ini:"skip_tls_verify"` // 仅自签名 / 内网 SMTP 需要
|
||||
}
|
||||
|
||||
Service struct {
|
||||
@@ -155,8 +136,6 @@ var (
|
||||
Prefix string `ini:"prefix"`
|
||||
ForwardURL string `ini:"forward_url"`
|
||||
}
|
||||
// TLSInsecureSkipVerify 跳过反向代理到后端服务的 TLS 证书校验
|
||||
// (仅内网后端使用自签名证书时需要,默认 false 校验证书)
|
||||
TLSInsecureSkipVerify bool `ini:"tls_insecure_skip_verify"`
|
||||
TLSInsecureSkipVerify bool `ini:"tls_insecure_skip_verify"` // 仅内网自签名后端需要
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user