feat: v2.0.0 完整代码优化升级
🔴 高优先级 (6项全部完成): - 数据库事务支持 (InsertMessageWithLog) - SQL注入修复 (参数化查询) - 配置验证 (Validate方法) - 会话密钥强化 (长度验证) - 签名验证增强 (SignVerificationResult) - 密码哈希支持 (bcrypt) 🟡 中优先级 (15项全部完成): - 连接池配置 (MaxOpenConns, MaxIdleConns) - 查询优化 (范围查询, 索引) - 健康检查增强 (/health 端点) - API版本控制 (/api/v1/*) - 认证中间件 (RequireAuth, RequireAPIAuth) - 定时任务优化 (robfig/cron) - 配置文件示例 (config.example.yaml) - 常量定义 (config/constants.go) - 开发文档 (DEVELOPMENT.md) 🟢 低优先级 (9项全部完成): - Docker支持 (Dockerfile, docker-compose.yml) - Makefile构建脚本 - 优化报告 (OPTIMIZATION_REPORT.md) - 密码哈希工具 (tools/password_hash.go) - 14个新文件 - 30项优化100%完成 版本: v2.0.0
This commit is contained in:
@@ -93,7 +93,7 @@ func Index(w http.ResponseWriter, r *http.Request) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := 20
|
||||
limit := config.DefaultPageSize
|
||||
from := r.URL.Query().Get("from")
|
||||
search := r.URL.Query().Get("search")
|
||||
|
||||
@@ -173,19 +173,32 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
cfg := config.Get()
|
||||
if cfg.Security.Enabled {
|
||||
if username == cfg.Security.Username && password == cfg.Security.Password {
|
||||
if err := auth.Login(w, r, username); err != nil {
|
||||
log.Printf("创建会话失败: %v", err)
|
||||
http.Error(w, "创建会话失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
// 验证用户名
|
||||
if username != cfg.Security.Username {
|
||||
templates.ExecuteTemplate(w, "login.html", map[string]string{
|
||||
"error": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
// 登录失败
|
||||
templates.ExecuteTemplate(w, "login.html", map[string]string{
|
||||
"error": "用户名或密码错误",
|
||||
})
|
||||
|
||||
// 验证密码(支持哈希和明文)
|
||||
if !auth.VerifyPassword(password, cfg.Security.PasswordHash, cfg.Security.Password) {
|
||||
// 记录登录失败日志
|
||||
log.Printf("登录失败: 用户=%s, IP=%s", username, getClientIP(r))
|
||||
templates.ExecuteTemplate(w, "login.html", map[string]string{
|
||||
"error": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建会话
|
||||
if err := auth.Login(w, r, username); err != nil {
|
||||
log.Printf("创建会话失败: %v", err)
|
||||
http.Error(w, "创建会话失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("登录成功: 用户=%s, IP=%s", username, getClientIP(r))
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -247,7 +260,7 @@ func Logs(w http.ResponseWriter, r *http.Request) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := 50
|
||||
limit := config.DefaultLogsPerPage
|
||||
|
||||
logs, total, err := database.GetLogs(page, limit)
|
||||
if err != nil {
|
||||
@@ -342,18 +355,26 @@ func ReceiveSMS(w http.ResponseWriter, r *http.Request) {
|
||||
cfg := config.Get()
|
||||
signValid := sql.NullBool{Bool: true, Valid: true}
|
||||
if token != "" && cfg.Security.SignVerify {
|
||||
valid, err := sign.VerifySign(token, timestamp, signStr, &cfg.Security)
|
||||
result, err := sign.VerifySign(token, timestamp, signStr, &cfg.Security)
|
||||
if err != nil {
|
||||
writeJSON(w, models.APIResponse{
|
||||
Success: false,
|
||||
Error: "签名验证错误",
|
||||
Error: "签名验证错误: " + err.Error(),
|
||||
}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
signValid.Bool = valid
|
||||
signValid.Bool = result.Valid
|
||||
signValid.Valid = true
|
||||
if !valid {
|
||||
signValid.Bool = false
|
||||
|
||||
// 记录签名的 IP 地址
|
||||
clientIP := getClientIP(r)
|
||||
if result.Valid {
|
||||
log.Printf("签名验证通过: token=%s, timestamp=%d, ip=%s, reason=%s",
|
||||
token, timestamp, clientIP, result.Reason)
|
||||
} else {
|
||||
log.Printf("签名验证失败: token=%s, timestamp=%d, ip=%s, reason=%s",
|
||||
token, timestamp, clientIP, result.Reason)
|
||||
// 签名验证失败时仍然记录消息(标记为未验证)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,30 +389,8 @@ func ReceiveSMS(w http.ResponseWriter, r *http.Request) {
|
||||
IPAddress: getClientIP(r),
|
||||
}
|
||||
|
||||
messageID, err := database.InsertMessage(msg)
|
||||
if err != nil {
|
||||
// 记录失败日志
|
||||
log := &models.ReceiveLog{
|
||||
FromNumber: from,
|
||||
Content: content,
|
||||
Timestamp: timestamp,
|
||||
Sign: sql.NullString{String: signStr, Valid: signStr != ""},
|
||||
SignValid: signValid,
|
||||
IPAddress: getClientIP(r),
|
||||
Status: "error",
|
||||
ErrorMessage: sql.NullString{String: err.Error(), Valid: true},
|
||||
}
|
||||
database.InsertLog(log)
|
||||
|
||||
writeJSON(w, models.APIResponse{
|
||||
Success: false,
|
||||
Error: "保存消息失败",
|
||||
}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 记录成功日志
|
||||
log := &models.ReceiveLog{
|
||||
receiveLog := &models.ReceiveLog{
|
||||
FromNumber: from,
|
||||
Content: content,
|
||||
Timestamp: timestamp,
|
||||
@@ -400,7 +399,22 @@ func ReceiveSMS(w http.ResponseWriter, r *http.Request) {
|
||||
IPAddress: getClientIP(r),
|
||||
Status: "success",
|
||||
}
|
||||
database.InsertLog(log)
|
||||
|
||||
// 使用事务同时插入消息和日志
|
||||
messageID, err := database.InsertMessageWithLog(msg, receiveLog)
|
||||
if err != nil {
|
||||
// 记录失败日志(尝试单独插入)
|
||||
receiveLog.Status = "error"
|
||||
receiveLog.ErrorMessage = sql.NullString{String: err.Error(), Valid: true}
|
||||
// 忽略日志插入错误,避免影响主错误返回
|
||||
_, _ = database.InsertLog(receiveLog)
|
||||
|
||||
writeJSON(w, models.APIResponse{
|
||||
Success: false,
|
||||
Error: "保存消息失败",
|
||||
}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, models.APIResponse{
|
||||
Success: true,
|
||||
@@ -422,10 +436,10 @@ func APIGetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
limit = config.DefaultPageSize
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
if limit > config.MaxPageSize {
|
||||
limit = config.MaxPageSize
|
||||
}
|
||||
from := r.URL.Query().Get("from")
|
||||
search := r.URL.Query().Get("search")
|
||||
|
||||
52
handlers/health.go
Normal file
52
handlers/health.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"sms-receiver-go/config"
|
||||
"sms-receiver-go/database"
|
||||
)
|
||||
|
||||
// HealthCheck 健康检查端点
|
||||
func HealthCheck(startTime time.Time) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cfg := config.Get()
|
||||
db := database.GetDB()
|
||||
|
||||
// 检查数据库连接
|
||||
dbStatus := "ok"
|
||||
if db == nil {
|
||||
dbStatus = "disconnected"
|
||||
} else if err := db.Ping(); err != nil {
|
||||
dbStatus = "error: " + err.Error()
|
||||
}
|
||||
|
||||
// 获取基本统计
|
||||
var totalMessages int64
|
||||
if dbStatus == "ok" {
|
||||
db.QueryRow("SELECT COUNT(*) FROM sms_messages").Scan(&totalMessages)
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"app_name": cfg.App.Name,
|
||||
"version": cfg.App.Version,
|
||||
"database": dbStatus,
|
||||
"total_messages": totalMessages,
|
||||
"uptime": time.Since(startTime).String(),
|
||||
}
|
||||
|
||||
// 如果数据库有问题,返回503
|
||||
statusCode := http.StatusOK
|
||||
if dbStatus != "ok" {
|
||||
response["status"] = "degraded"
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
38
handlers/middleware.go
Normal file
38
handlers/middleware.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"sms-receiver-go/auth"
|
||||
"sms-receiver-go/config"
|
||||
"sms-receiver-go/models"
|
||||
)
|
||||
|
||||
// RequireAuth 要求登录的中间件
|
||||
func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
loggedIn, _ := auth.CheckLogin(w, r)
|
||||
if !loggedIn {
|
||||
return // CheckLogin 已经处理重定向
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAPIAuth API 鉴权中间件
|
||||
func RequireAPIAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cfg := config.Get()
|
||||
if cfg.Security.Enabled {
|
||||
loggedIn, _ := auth.IsLoggedIn(r)
|
||||
if !loggedIn {
|
||||
writeJSON(w, models.APIResponse{
|
||||
Success: false,
|
||||
Error: "未授权",
|
||||
}, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user