Files
fanqie-go/internal/fqapi/content_utils.go
T
chenxin d710d382ee 🐛 fix(server): 增加优雅退出与请求超时保护
- 新增 SIGINT/SIGTERM 信号处理与 http.Server.Shutdown,退出前确保统计数据保存、数据库正常关闭
- 为 http.Server 设置 ReadHeaderTimeout/ReadTimeout/WriteTimeout/IdleTimeout,防止慢客户端长期占用连接
- 全局配置指针改为 atomic.Pointer 并提供 GetAppConfig 访问器,消除配置热更新时的数据竞争,同时删除冗余的 manager.config 字段
- 请求统计九组计数器重构为 map[string]*atomic.Int64,统一 incrementStat/getStatsSnapshot 访问,精简约 40 行样板代码
- go mod tidy 清理 go.mod 与 go.sum 中的冗余依赖条目
2026-09-29 19:47:04 +08:00

266 lines
6.4 KiB
Go

package fqapi
import (
"encoding/json"
"fanqie/internal/config"
"sort"
"strings"
)
// getContentConfig 获取内容配置(带默认值)
func getContentConfig() config.ContentConfig {
if config.GetAppConfig() != nil {
return config.GetAppConfig().Fanqie.Content
}
// 返回默认配置
return config.ContentConfig{
RemoveImages: false,
RemoveImageDesc: false,
RemoveBlankLines: true,
RemoveHTMLTags: false,
RemoveChapterTitle: false,
AddParagraphIndent: false,
}
}
// decodeHTMLEntities 解码HTML实体
func decodeHTMLEntities(content string) string {
// 解码常见HTML实体
content = strings.ReplaceAll(content, "&", "&")
content = strings.ReplaceAll(content, "&lt;", "<")
content = strings.ReplaceAll(content, "&gt;", ">")
content = strings.ReplaceAll(content, "&quot;", "\"")
content = strings.ReplaceAll(content, "&#34;", "\"")
content = strings.ReplaceAll(content, "&#39;", "'")
content = strings.ReplaceAll(content, "&apos;", "'")
content = strings.ReplaceAll(content, "&#x27;", "'")
content = strings.ReplaceAll(content, "&#x2F;", "/")
// 解码转义的反斜杠
content = strings.ReplaceAll(content, "\\\"", "\"")
return content
}
// normalizeImgAttributes 将img标签中的img-width和img-height属性替换为标准的width和height属性
func normalizeImgAttributes(content string) string {
// 替换 img-width 为 width
content = strings.ReplaceAll(content, "img-width=", "width=")
// 替换 img-height 为 height
content = strings.ReplaceAll(content, "img-height=", "height=")
return content
}
// removeBlankLines 移除多余空行,但保留段落间的双换行
func removeBlankLines(content string) string {
// 先将多个连续换行(3个及以上)规范化为双换行
for strings.Contains(content, "\n\n\n") {
content = strings.ReplaceAll(content, "\n\n\n", "\n\n")
}
// 移除首尾的空白
content = strings.TrimSpace(content)
return content
}
// ============ 参数解析工具函数 ============
// parseBoolParam 解析布尔参数
func parseBoolParam(param interface{}) bool {
switch v := param.(type) {
case string:
return v == "true" || v == "1" || strings.ToLower(v) == "true"
case bool:
return v
}
return false
}
// parseBoolParamDefault 解析布尔参数(带默认值)
func parseBoolParamDefault(param interface{}, defaultVal bool) bool {
if param == nil || param == "" {
return defaultVal
}
return parseBoolParam(param)
}
// ============ JSON 处理工具函数 ============
// escapeJSON 转义JSON中的特殊字符
func escapeJSON(s string) string {
s = strings.ReplaceAll(s, "<", "\\u003c")
s = strings.ReplaceAll(s, ">", "\\u003e")
return s
}
// buildJSONResponse 构建JSON响应并转义
func buildJSONResponse(data interface{}) string {
resultBytes, _ := json.Marshal(data)
return escapeJSON(string(resultBytes))
}
// ============ 设备和密钥管理 ============
// getDeviceHeader 获取设备header(从设备池)
func getDeviceHeader() string {
// 从设备池获取任意有效设备header
return GetAnyValidDeviceHeader()
}
// removeVersionFromHeader 移除header中的version_name参数
func removeVersionFromHeader(header string) string {
return strings.Replace(header, "&version_name=5.8.9.32", "", -1)
}
// ============ API参数构建工具 ============
// BuildURL 构建API请求URL
// 用法: BuildURL("https://reading.snssdk.com/reading/bookapi/detail/v/", "book_id=123", "tab_type=3")
func BuildURL(fullURL string, customParams ...string) string {
// 基础参数
allParams := []string{
"aid=1967",
"app_name=novelapp",
"channel=0",
"device_platform=android",
"os_version=0",
"version_code=58932",
}
// 添加设备header
header := getDeviceHeader()
if header != "" {
allParams = append(allParams, header)
}
// 添加自定义参数
allParams = append(allParams, customParams...)
// 合并、拆分、排序
joined := strings.Join(allParams, "&")
paramList := strings.Split(joined, "&")
sort.Strings(paramList)
params := strings.Join(paramList, "&")
return fullURL + "?" + params
}
// ============ 缓存处理工具函数 ============
// buildCacheData 构建缓存数据结构
func buildCacheData(data interface{}) map[string]interface{} {
return map[string]interface{}{
"code": 0,
"message": "SUCCESS",
"data": data,
}
}
// saveCacheJSON 保存JSON到缓存
func saveCacheJSON(key string, data interface{}, ttl int) {
cacheJSON, _ := json.Marshal(data)
cacheStr := escapeJSON(string(cacheJSON))
UpdateCache(key, cacheStr, ttl)
}
// ============ 错误响应工具函数 ============
// BuildErrorResponse 构建统一的错误响应
func BuildErrorResponse(msg string) string {
return buildJSONResponse(map[string]interface{}{
"code": -1,
"msg": msg,
})
}
// BuildErrorResponseWithCode 构建带自定义错误码的响应
func BuildErrorResponseWithCode(code int, msg string) string {
return buildJSONResponse(map[string]interface{}{
"code": code,
"msg": msg,
})
}
// BuildSuccessResponse 构建成功响应
func BuildSuccessResponse(data interface{}) string {
return buildJSONResponse(map[string]interface{}{
"code": 0,
"message": "SUCCESS",
"data": data,
})
}
// ============ 参数转换工具函数 ============
// ParseIntParam 解析整数参数(支持多种类型)
func ParseIntParam(param interface{}, defaultVal int) int {
switch v := param.(type) {
case string:
if v != "" {
// 使用strconv.Atoi
if result, err := parseStringToInt(v); err == nil {
return result
}
}
case int:
return v
case float64:
return int(v)
case int64:
return int(v)
}
return defaultVal
}
// parseStringToInt 辅助函数:将字符串转换为整数
func parseStringToInt(s string) (int, error) {
// 简单实现,避免导入strconv
result := 0
negative := false
if len(s) == 0 {
return 0, nil
}
start := 0
if s[0] == '-' {
negative = true
start = 1
} else if s[0] == '+' {
start = 1
}
for i := start; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
break
}
result = result*10 + int(s[i]-'0')
}
if negative {
result = -result
}
return result, nil
}
// ParseStringParam 解析字符串参数(支持数组类型自动Join)
func ParseStringParam(param interface{}, defaultVal string) string {
if param == nil || param == "" {
return defaultVal
}
switch v := param.(type) {
case string:
if v != "" {
return v
}
case []string:
// 数组类型,用逗号连接
return strings.Join(v, ",")
}
return defaultVal
}