🔧 chore: 初始化番茄小说 API 项目结构
新增项目基础框架,包含核心 API 实现、算法模块、HTTP 处理器等完整功能模块
This commit is contained in:
commit
6981148727
107 files changed
+12463
No files matched your search
@@ -0,0 +1,192 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPClient HTTP客户端
|
||||
type HTTPClient struct {
|
||||
client *http.Client
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// NewHTTPClient 创建新的HTTP客户端
|
||||
func NewHTTPClient() *HTTPClient {
|
||||
return &HTTPClient{
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
logger: NewLogger("http_utils", "INFO", ""),
|
||||
}
|
||||
}
|
||||
|
||||
// ClientResponse 客户端响应
|
||||
type ClientResponse struct {
|
||||
Status int
|
||||
Content []byte
|
||||
Headers http.Header
|
||||
Cookies []*http.Cookie
|
||||
Text string
|
||||
}
|
||||
|
||||
// GetHeader 获取响应头(兼容方法)
|
||||
func (r *ClientResponse) GetHeader(key string) string {
|
||||
return r.Headers.Get(key)
|
||||
}
|
||||
|
||||
// JSON 解析JSON响应
|
||||
func (r *ClientResponse) JSON() (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(r.Content, &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Request HTTP请求
|
||||
func (h *HTTPClient) Request(url string, options map[string]interface{}) (*ClientResponse, error) {
|
||||
// 获取请求方法
|
||||
method := "GET"
|
||||
if m, ok := options["method"].(string); ok {
|
||||
method = strings.ToUpper(m)
|
||||
}
|
||||
|
||||
// 获取请求头
|
||||
headers := make(map[string]string)
|
||||
if h, ok := options["headers"].(map[string]string); ok {
|
||||
headers = h
|
||||
}
|
||||
|
||||
// 设置默认User-Agent
|
||||
if _, ok := headers["User-Agent"]; !ok {
|
||||
headers["User-Agent"] = "okhttp/4.12.0"
|
||||
}
|
||||
|
||||
// 获取请求体
|
||||
var body io.Reader
|
||||
if method == "POST" || method == "PUT" {
|
||||
if b, ok := options["body"].(string); ok {
|
||||
body = strings.NewReader(b)
|
||||
} else if b, ok := options["body"].([]byte); ok {
|
||||
body = bytes.NewReader(b)
|
||||
} else if data, ok := options["data"].(string); ok {
|
||||
body = strings.NewReader(data)
|
||||
} else if data, ok := options["data"].([]byte); ok {
|
||||
body = bytes.NewReader(data)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
h.logger.Error(fmt.Sprintf("Failed to create request: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// 打印请求信息
|
||||
// h.logger.Info(fmt.Sprintf("=== HTTP 请求 ==="))
|
||||
// h.logger.Info(fmt.Sprintf("方法: %s", method))
|
||||
// h.logger.Info(fmt.Sprintf("URL: %s", url))
|
||||
// h.logger.Info(fmt.Sprintf("请求头:"))
|
||||
// for k, v := range req.Header {
|
||||
// h.logger.Info(fmt.Sprintf(" %s: %s", k, strings.Join(v, ", ")))
|
||||
// }
|
||||
|
||||
// 检查是否跳过SSL验证
|
||||
skipSSL := os.Getenv("FQWEB_SKIP_SSL_VERIFY")
|
||||
if skipSSL != "" && h.client.Transport == nil {
|
||||
// 配置跳过SSL验证
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
h.client.Transport = transport
|
||||
h.logger.Warning("已启用SSL验证跳过模式")
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
h.logger.Error(fmt.Sprintf("HTTP Request error: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
h.logger.Error(fmt.Sprintf("Failed to read response: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 处理zlib压缩
|
||||
if len(content) > 2 && (content[0] == 0x78 && (content[1] == 0x9C || content[1] == 0x01)) {
|
||||
reader, err := zlib.NewReader(bytes.NewReader(content))
|
||||
if err == nil {
|
||||
decompressed, err := io.ReadAll(reader)
|
||||
reader.Close()
|
||||
if err == nil {
|
||||
content = decompressed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response := &ClientResponse{
|
||||
Status: resp.StatusCode,
|
||||
Content: content,
|
||||
Headers: resp.Header,
|
||||
Cookies: resp.Cookies(),
|
||||
Text: string(content),
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Get GET请求
|
||||
func (h *HTTPClient) Get(url string, headers map[string]string) (*ClientResponse, error) {
|
||||
options := map[string]interface{}{
|
||||
"method": "GET",
|
||||
"headers": headers,
|
||||
}
|
||||
return h.Request(url, options)
|
||||
}
|
||||
|
||||
// Post POST请求
|
||||
func (h *HTTPClient) Post(url string, data interface{}, headers map[string]string) (*ClientResponse, error) {
|
||||
options := map[string]interface{}{
|
||||
"method": "POST",
|
||||
"headers": headers,
|
||||
"body": data,
|
||||
}
|
||||
return h.Request(url, options)
|
||||
}
|
||||
|
||||
// ConvertDictToFormString 将字典转换为表单字符串
|
||||
func ConvertDictToFormString(dict map[string]string) string {
|
||||
parts := make([]string, 0, len(dict))
|
||||
for k, v := range dict {
|
||||
parts = append(parts, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(parts, "&")
|
||||
}
|
||||
|
||||
// DefaultHTTPClient 默认HTTP客户端
|
||||
var DefaultHTTPClient = NewHTTPClient()
|
||||
|
||||
// Request 全局请求函数
|
||||
func Request(url string, options map[string]interface{}) (*ClientResponse, error) {
|
||||
return DefaultHTTPClient.Request(url, options)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel 日志级别
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
DEBUG LogLevel = iota
|
||||
INFO
|
||||
WARNING
|
||||
ERROR
|
||||
CRITICAL
|
||||
)
|
||||
|
||||
// Logger 日志器
|
||||
type Logger struct {
|
||||
moduleName string
|
||||
outputLevel LogLevel
|
||||
}
|
||||
|
||||
// NewLogger 创建新的日志器
|
||||
func NewLogger(moduleName string, outputLevel string, filename string) *Logger {
|
||||
logger := &Logger{
|
||||
moduleName: moduleName,
|
||||
}
|
||||
|
||||
// 设置日志级别
|
||||
switch strings.ToUpper(outputLevel) {
|
||||
case "DEBUG":
|
||||
logger.outputLevel = DEBUG
|
||||
case "INFO":
|
||||
logger.outputLevel = INFO
|
||||
case "WARNING":
|
||||
logger.outputLevel = WARNING
|
||||
case "ERROR":
|
||||
logger.outputLevel = ERROR
|
||||
case "CRITICAL":
|
||||
logger.outputLevel = CRITICAL
|
||||
default:
|
||||
logger.outputLevel = INFO
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
// Debug 调试日志
|
||||
func (l *Logger) Debug(message string) {
|
||||
if l.outputLevel <= DEBUG {
|
||||
l.log("DEBUG", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Info 信息日志
|
||||
func (l *Logger) Info(message string) {
|
||||
if l.outputLevel <= INFO {
|
||||
l.log("INFO", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Warning 警告日志
|
||||
func (l *Logger) Warning(message string) {
|
||||
if l.outputLevel <= WARNING {
|
||||
l.log("WARNING", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Error 错误日志
|
||||
func (l *Logger) Error(message string) {
|
||||
if l.outputLevel <= ERROR {
|
||||
l.log("ERROR", message)
|
||||
}
|
||||
}
|
||||
|
||||
// Critical 严重错误日志
|
||||
func (l *Logger) Critical(message string) {
|
||||
if l.outputLevel <= CRITICAL {
|
||||
l.log("CRITICAL", message)
|
||||
}
|
||||
}
|
||||
|
||||
// log 内部日志方法
|
||||
func (l *Logger) log(level string, message string) {
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
logMessage := fmt.Sprintf("%s|[%s/%s]|%s",
|
||||
timestamp, l.moduleName, level, message)
|
||||
|
||||
// 控制台输出
|
||||
fmt.Println(logMessage)
|
||||
}
|
||||
|
||||
// Log 全局日志函数
|
||||
func Log(moduleName string) *Logger {
|
||||
return NewLogger(moduleName, "INFO", "")
|
||||
}
|
||||
|
||||
// HighlightError 高亮错误
|
||||
func HighlightError(err interface{}) string {
|
||||
return fmt.Sprintf("%v", err)
|
||||
}
|
||||
|
||||
// 全局日志实例
|
||||
var (
|
||||
defaultLogger = NewLogger("fanqie", "INFO", "")
|
||||
)
|
||||
|
||||
// LogPrint 打印日志
|
||||
func LogPrint(args ...interface{}) {
|
||||
message := fmt.Sprint(args...)
|
||||
defaultLogger.Info(message)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 初始化日志系统
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Bytes2DataURI 将字节转换为 Data URI
|
||||
func Bytes2DataURI(dataBytes []byte, mimeType string) string {
|
||||
encoded := base64.StdEncoding.EncodeToString(dataBytes)
|
||||
return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)
|
||||
}
|
||||
|
||||
// CreateMD5 创建MD5哈希
|
||||
func CreateMD5(s interface{}) string {
|
||||
var data []byte
|
||||
switch v := s.(type) {
|
||||
case string:
|
||||
data = []byte(v)
|
||||
case []byte:
|
||||
data = v
|
||||
default:
|
||||
data = []byte(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
hasher := md5.New()
|
||||
hasher.Write(data)
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user