Initial commit: 可转债轮动分析系统

- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换)
- 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制)
- 增量同步:支持 -incr/-since 参数按存档日期增量入库
- 策略回测:净值曲线/10指标/基准对比,含基本面过滤
- 用户认证:用户名密码 + 邮箱验证码双登录方式
- 前端:三Tab SPA(实时行情/策略回测/数据管理)
This commit is contained in:
2026-08-17 20:23:22 +08:00
commit e28bfec957
45 changed files with 7889 additions and 0 deletions

168
internal/controller/auth.go Normal file
View File

@@ -0,0 +1,168 @@
// Package controller 定义 MVC 架构中的 Controller 层。
package controller
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"stock-analysis/internal/model"
"stock-analysis/internal/service"
)
// AuthController 提供用户认证相关的 HTTP 接口。
type AuthController struct {
svc *service.AuthService
}
// NewAuthController 创建认证控制器。
func NewAuthController(svc *service.AuthService) *AuthController {
return &AuthController{svc: svc}
}
// CurrentUser 返回当前请求对应的登录用户,未登录返回错误。
func (c *AuthController) CurrentUser(r *http.Request) (*model.User, error) {
token := extractToken(r)
if token == "" {
return nil, fmt.Errorf("未登录")
}
return c.svc.ValidateToken(r.Context(), token)
}
// sendCodeRequest 发送验证码请求。
type sendCodeRequest struct {
Email string `json:"email"`
Purpose string `json:"purpose"` // register 或 login
}
// SendCode 处理 POST /api/auth/send-code,发送邮箱验证码。
func (c *AuthController) SendCode(w http.ResponseWriter, r *http.Request) {
var req sendCodeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
if req.Purpose == "" {
req.Purpose = "register"
}
if err := c.svc.SendVerifyCode(r.Context(), req.Email, req.Purpose); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
writeJSON(w, http.StatusOK, map[string]string{"message": "验证码已发送"})
}
// registerRequest 注册请求。
type registerRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Code string `json:"code"`
Password string `json:"password"`
}
// Register 处理 POST /api/auth/register,邮箱+验证码注册。
func (c *AuthController) Register(w http.ResponseWriter, r *http.Request) {
var req registerRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
user, err := c.svc.Register(r.Context(), req.Username, req.Email, req.Code, req.Password)
if err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"id": user.ID,
"username": user.Username,
"email": user.Email,
})
}
// loginRequest 登录请求。
type loginRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Code string `json:"code"`
Password string `json:"password"`
}
// Login 处理 POST /api/auth/login。
// 支持两种登录方式:
// - 用户名+密码:{ username, password }
// - 邮箱+验证码:{ email, code }
func (c *AuthController) Login(w http.ResponseWriter, r *http.Request) {
var req loginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, err)
return
}
var user *model.User
var token string
var err error
if req.Username != "" {
// 用户名+密码登录
user, token, err = c.svc.LoginByUsername(r.Context(), req.Username, req.Password)
} else if req.Code != "" {
// 邮箱+验证码登录
user, token, err = c.svc.Login(r.Context(), req.Email, req.Code)
} else {
writeError(w, http.StatusBadRequest, fmt.Errorf("请输入用户名密码或邮箱验证码"))
return
}
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"id": user.ID,
"username": user.Username,
"email": user.Email,
"token": token,
})
}
// Me 处理 GET /api/auth/me,返回当前登录用户信息。
func (c *AuthController) Me(w http.ResponseWriter, r *http.Request) {
token := extractToken(r)
if token == "" {
writeError(w, http.StatusUnauthorized, fmt.Errorf("未登录"))
return
}
u, err := c.svc.ValidateToken(r.Context(), token)
if err != nil {
writeError(w, http.StatusUnauthorized, err)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"id": u.ID,
"username": u.Username,
"email": u.Email,
})
}
// Logout 处理 POST /api/auth/logout,注销当前会话。
func (c *AuthController) Logout(w http.ResponseWriter, r *http.Request) {
token := extractToken(r)
if token != "" {
c.svc.Logout(token)
}
writeJSON(w, http.StatusOK, map[string]string{"message": "已退出"})
}
// extractToken 从 Cookie 或 Authorization 头中提取 token。
func extractToken(r *http.Request) string {
if c, err := r.Cookie("auth_token"); err == nil && c.Value != "" {
return c.Value
}
if h := r.Header.Get("Authorization"); h != "" {
if strings.HasPrefix(h, "Bearer ") {
return strings.TrimPrefix(h, "Bearer ")
}
}
return ""
}