feat: 站点访问统计与可配置侧边栏组件

- 新增访问统计:visitstats 服务与查询、visit_capture 中间件、爬虫/来源识别、管理端 analytics 页面
- 新增侧边栏:sidebar 服务与 handler、管理端 sidebar 配置页、前端侧边栏组件
- 新增 widget 嵌入代码生成(widgetCode)与 widgetApi
- 遥测逻辑迁移至 handler 层,删除 service/telemetry
- env 示例补充 Docker 部署可信代理 IP 说明
This commit is contained in:
2026-09-24 18:53:45 +08:00
parent 3e55b5d230
commit 7b327bc8cc
60 changed files with 6539 additions and 572 deletions

View File

@@ -53,6 +53,10 @@ const (
SettingKeyAttachmentMaxCount = "attachment_max_count"
// SettingKeyImageMaxMB 正文插图上限(MB);缺行=5
SettingKeyImageMaxMB = "image_max_mb"
// SettingKeyAnalyticsEnabled 访问统计采集开关;缺行视为开启(默认开)
SettingKeyAnalyticsEnabled = "analytics_enabled"
// SettingKeyAnalyticsRetentionDays 访问明细保留天数;缺行=90
SettingKeyAnalyticsRetentionDays = "analytics_retention_days"
)
const (
@@ -74,6 +78,10 @@ const (
MinAttachmentMaxCount = 1
MaxAttachmentMaxCount = 20
MaxAttachmentExtCount = 80
DefaultAnalyticsRetentionDays = 90
MinAnalyticsRetentionDays = 7
MaxAnalyticsRetentionDays = 365
)
// DefaultAttachmentExts 论坛向默认允许扩展名(含安装包/脚本/网页)
@@ -880,3 +888,47 @@ func (s *SettingService) ImageMaxBytes() (int64, error) {
}
return int64(mb) << 20, nil
}
// AnalyticsEnabled 访问统计采集开关。缺行视为开启。
func (s *SettingService) AnalyticsEnabled() (bool, error) {
v, found, err := s.getValue(SettingKeyAnalyticsEnabled)
if err != nil {
return true, err
}
return parseBoolDefaultTrue(v, found), nil
}
// SetAnalyticsEnabled 持久化采集开关;开启时删键保持「缺行=默认开」。
func (s *SettingService) SetAnalyticsEnabled(on bool) error {
return s.setBoolDefaultTrue(SettingKeyAnalyticsEnabled, on)
}
// AnalyticsRetentionDays 访问明细保留天数。缺行=90;钳制 7–365。
func (s *SettingService) AnalyticsRetentionDays() (int, error) {
v, found, err := s.getValue(SettingKeyAnalyticsRetentionDays)
if err != nil {
return DefaultAnalyticsRetentionDays, err
}
if !found {
return DefaultAnalyticsRetentionDays, nil
}
n, convErr := strconv.Atoi(strings.TrimSpace(v))
if convErr != nil || n < MinAnalyticsRetentionDays {
return DefaultAnalyticsRetentionDays, nil
}
if n > MaxAnalyticsRetentionDays {
return MaxAnalyticsRetentionDays, nil
}
return n, nil
}
// SetAnalyticsRetentionDays 持久化保留天数;等于默认值时删键。
func (s *SettingService) SetAnalyticsRetentionDays(days int) error {
if days < MinAnalyticsRetentionDays || days > MaxAnalyticsRetentionDays {
return ErrInvalidSiteSetting
}
if days == DefaultAnalyticsRetentionDays {
return s.deleteKey(SettingKeyAnalyticsRetentionDays)
}
return s.putValue(SettingKeyAnalyticsRetentionDays, strconv.Itoa(days))
}