package handlers
import (
"encoding/json"
"fanqie/internal/algos"
"fanqie/internal/config"
"fanqie/internal/fqapi"
"fmt"
"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": "数据库压缩完成",
})
}
// 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()
html := fmt.Sprintf(`
番茄小说API - 运行状态
● 运行状态
服务器时间
%s
运行时长
%s
启动时间
%s
签名方法
%s
数据库大小
%s
已用空间
%s
空闲空间
%s 立即压缩
◉ 设备密钥池
池状态
%s
总设备数
%d
有效设备
%d 可用
无效设备
%d
当前索引
%d
■ 请求统计
总请求数
%d
搜索请求
%d
详情请求
%d
目录请求
%d
内容请求
%d
漫画请求
%d
音频请求
%d
视频请求
%d
分类请求
%d
◆ 缓存统计
正文缓存数
%d
正文缓存
%s
自动清理
%s
清理间隔
%s
◈ API 接口文档
书籍相关
GET/search
◆ 搜索书籍
query
必需
搜索关键字
offset
可选
偏移量,默认0
tab_type
可选
类型,默认3
GET/multi-detail
◆ 批量获取书籍详情
内容相关
GET/content
◆ 获取章节正文内容
item_id
必需
章节ID,支持路径参数或查询参数
text_mode
可选
纯文本模式:1/true=纯文本,0/false=HTML
image_mode
可选
图片处理:1/true=保留图片,0/false=移除图片
review
可选
段评功能:1=启用,0=禁用
GET/multi-content
◆ 批量获取多个章节正文
book_id
必需
书籍ID
item_ids
必需
章节ID列表,多个ID用逗号分隔,如:123,456,789
text_mode
可选
纯文本模式:1/true=纯文本,0/false=HTML
image_mode
可选
图片处理:1/true=保留图片,0/false=移除图片
ignore_failed
可选
忽略失败章节:1/true=忽略,0/false=不忽略
GET/item-summary
◆ 获取章节摘要信息
book_id
必需
书籍ID
item_ids
必需
章节ID列表,逗号分隔
段评相关
GET/review
◆ 获取段落评论、回复
item_id
必需
章节ID
book_id
必需
书籍ID
para
必需
段落索引(从0开始)
comment_id
可选
评论ID(获取回复时需要)
GET/review_html
◆ 获取段落评论HTML页面
item_id
必需
章节ID
book_id
必需
书籍ID
para
必需
段落索引(从0开始)
多媒体
GET/manga
◆ 获取漫画内容
item_ids
必需
章节ID
output
可选
输出格式:url/proxy/data_uri,默认url
GET/audio
◆ 获取音频信息
item_ids
必需
章节ID
tone_id
可选
音色ID,默认1
分类
GET/category-front
◆ 获取分类首页
GET/category-landing
◆ 获取分类详情页
category_id
必需
分类ID
offset
可选
偏移量,默认0
limit
可选
每页数量,默认20
系统
GET/status
◆ 系统状态页面(当前页面)
GET/self-info
◆ 设备信息(包含敏感数据)
`,
time.Now().Format("2006-01-02 15:04:05"),
uptimeStr,
startTime.Format("2006-01-02 15:04:05"),
signatureDetails,
getDBTotalSize(),
getDBUsedSize(),
getDBFreeSize(),
poolStatus["status"],
poolStatus["total"],
poolStatus["valid"],
poolStatus["invalid"],
poolStatus["current_index"],
stats["total"],
stats["search"],
stats["detail"],
stats["catalog"],
stats["content"],
stats["manga"],
stats["audio"],
stats["video"],
stats["category"],
getDBCacheCount(),
getCacheStatus(),
getCleanupStatus(),
getCleanupInterval(),
)
fmt.Fprint(w, html)
}
// 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"],
}
}