Files
fanqie-go/internal/handlers/status.go
T
chenxin 5bcc8a962e ♻️ refactor(handlers): status 与 review 页面模板化
- HTML 提取至 templates/status.html、templates/review.html(go:embed 内嵌)
- status.go 1022→271 行、review.go 1281→438 行,动态数据全部经 html/template 按上下文自动转义
- 评论循环/作者徽章/贴纸/图片条件渲染翻译为模板 range/if;emojiMap 以 template.JS 注入
- 与旧版渲染输出实测对比:22 条评论结构完全一致;差异仅为 JS 上下文安全转义(& 等)与 html/template 剥除 CSS 注释,均无功能影响
- 其余文件为 gofmt 顺手格式化
2026-09-30 00:25:09 +08:00

271 lines
6.7 KiB
Go

package handlers
import (
_ "embed"
"encoding/json"
"fanqie/internal/algos"
"fanqie/internal/config"
"fanqie/internal/fqapi"
"fmt"
"html/template"
"net/http"
"time"
)
// HandleVacuum 处理数据库压缩请求
func HandleVacuum(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 只允许 POST 请求
if r.Method != http.MethodPost {
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "仅支持 POST 请求",
})
return
}
// 执行 VACUUM
err := fqapi.VacuumDatabase()
if err != nil {
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "压缩失败: " + err.Error(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"message": "数据库压缩完成",
})
}
// 状态页模板(embed 内嵌,动态数据由 html/template 自动转义)
//
//go:embed templates/status.html
var statusHTML string
var statusTmpl = template.Must(template.New("status").Parse(statusHTML))
// HandleStatus 状态页面处理
func HandleStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
uptime := time.Since(startTime)
// 格式化运行时间
days := int(uptime.Hours() / 24)
hours := int(uptime.Hours()) % 24
minutes := int(uptime.Minutes()) % 60
seconds := int(uptime.Seconds()) % 60
var uptimeStr string
if days > 0 {
uptimeStr = fmt.Sprintf("%d天 %d小时 %d分钟 %d秒", days, hours, minutes, seconds)
} else if hours > 0 {
uptimeStr = fmt.Sprintf("%d小时 %d分钟 %d秒", hours, minutes, seconds)
} else if minutes > 0 {
uptimeStr = fmt.Sprintf("%d分钟 %d秒", minutes, seconds)
} else {
uptimeStr = fmt.Sprintf("%d秒", seconds)
}
// 获取签名信息
currentSign := algos.GlobalSignatureSwitcher.GetCurrentMethod()
// 生成签名详情显示
var signatureDetails string
if currentSign == "sign2" {
signatureDetails = "X-Gorgon + X-Khronos"
} else {
signatureDetails = "X-Gorgon + X-Ladon + X-Argus + X-Khronos"
}
// 获取统计数据
stats := getStatsSnapshot()
// 获取设备密钥池状态
poolStatus := getDeviceKeyPoolStatus()
statusTmpl.Execute(w, map[string]interface{}{
"NowTime": time.Now().Format("2006-01-02 15:04:05"),
"Uptime": uptimeStr,
"StartTime": startTime.Format("2006-01-02 15:04:05"),
"Signature": signatureDetails,
"DBTotal": getDBTotalSize(),
"DBUsed": getDBUsedSize(),
"DBFree": getDBFreeSize(),
"PoolStatus": poolStatus["status"],
"PoolTotal": poolStatus["total"],
"PoolValid": poolStatus["valid"],
"PoolInvalid": poolStatus["invalid"],
"PoolIndex": poolStatus["current_index"],
"StatTotal": stats["total"],
"StatSearch": stats["search"],
"StatDetail": stats["detail"],
"StatCatalog": stats["catalog"],
"StatContent": stats["content"],
"StatManga": stats["manga"],
"StatAudio": stats["audio"],
"StatVideo": stats["video"],
"StatCategory": stats["category"],
"CacheCount": getDBCacheCount(),
"CacheStatus": getCacheStatus(),
"CleanupStatus": getCleanupStatus(),
"CleanupInterval": getCleanupInterval(),
})
}
// getCacheStatus 获取缓存状态
func getCacheStatus() string {
cfg := config.GetConfig()
if cfg != nil && cfg.Cache.Enabled {
return "已启用"
}
return "已禁用"
}
// getCleanupStatus 获取清理状态
func getCleanupStatus() string {
cfg := config.GetConfig()
if cfg != nil && cfg.Cache.CleanupEnabled {
return "已启用"
}
return "已禁用"
}
// getCleanupInterval 获取清理间隔
func getCleanupInterval() string {
cfg := config.GetConfig()
if cfg != nil {
seconds := cfg.Cache.CleanupSeconds
if seconds == 0 {
return "不清理"
} else if seconds < 60 {
return fmt.Sprintf("%d秒", seconds)
} else if seconds < 3600 {
return fmt.Sprintf("%d分钟", seconds/60)
} else {
return fmt.Sprintf("%d小时", seconds/3600)
}
}
return "未知"
}
// formatBytes 格式化字节大小
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// getDBCacheCount 获取数据库缓存记录数
func getDBCacheCount() int {
stats, err := fqapi.GetDatabaseStats()
if err != nil || stats == nil {
return 0
}
if count, ok := stats["cache_count"].(int); ok {
return count
}
return 0
}
// getDBTotalSize 获取数据库总大小
func getDBTotalSize() string {
stats, err := fqapi.GetDatabaseStats()
if err != nil || stats == nil {
return "未知"
}
if size, ok := stats["total_size_bytes"].(int64); ok {
return formatBytes(size)
}
return "未知"
}
// getDBUsedSize 获取数据库已用空间
func getDBUsedSize() string {
stats, err := fqapi.GetDatabaseStats()
if err != nil || stats == nil {
return "未知"
}
if size, ok := stats["used_size_bytes"].(int64); ok {
return formatBytes(size)
}
return "未知"
}
// getDBFreeSize 获取数据库空闲空间(带颜色提示)
func getDBFreeSize() string {
stats, err := fqapi.GetDatabaseStats()
if err != nil || stats == nil {
return "未知"
}
freeSize, okFree := stats["free_size_bytes"].(int64)
totalSize, okTotal := stats["total_size_bytes"].(int64)
if !okFree {
return "未知"
}
if freeSize == 0 {
return "0 B"
}
freeSizeStr := formatBytes(freeSize)
// 如果有总大小,计算并显示百分比
if okTotal && totalSize > 0 {
percentage := float64(freeSize) * 100 / float64(totalSize)
return fmt.Sprintf("%s (%.1f%%)", freeSizeStr, percentage)
}
return freeSizeStr
}
// getDBUsagePercentage 获取数据库使用率
func getDBUsagePercentage() float64 {
stats, err := fqapi.GetDatabaseStats()
if err != nil || stats == nil {
return 0
}
usedSize, okUsed := stats["used_size_bytes"].(int64)
totalSize, okTotal := stats["total_size_bytes"].(int64)
if !okUsed || !okTotal || totalSize == 0 {
return 0
}
return float64(usedSize) * 100 / float64(totalSize)
}
// getDeviceKeyPoolStatus 获取设备密钥池状态
func getDeviceKeyPoolStatus() map[string]interface{} {
pool := fqapi.GetDeviceKeyPool()
if !pool.IsEnabled() {
return map[string]interface{}{
"status": "未启用",
"total": 0,
"valid": 0,
"invalid": 0,
"current_index": 0,
}
}
stats := pool.GetStats()
return map[string]interface{}{
"status": "已启用",
"total": stats["total"],
"valid": stats["valid"],
"invalid": stats["invalid"],
"current_index": stats["current_index"],
}
}