feat: 初始化 kenaito-notebook 桌面记事本项目
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"kenaito-notebook-server/services"
|
||||
)
|
||||
|
||||
func isValidEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type AuthHandler struct {
|
||||
db *services.Database
|
||||
auth *services.AuthService
|
||||
email *services.EmailService
|
||||
verify *services.VerificationService
|
||||
}
|
||||
|
||||
func NewAuthHandler(db *services.Database, auth *services.AuthService, email *services.EmailService, verify *services.VerificationService) *AuthHandler {
|
||||
return &AuthHandler{db: db, auth: auth, email: email, verify: verify}
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type SendCodeRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Purpose string `json:"purpose" binding:"required"`
|
||||
}
|
||||
|
||||
type VerifyCodeRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Purpose string `json:"purpose" binding:"required"`
|
||||
}
|
||||
|
||||
type ResetPasswordRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (h *AuthHandler) SendCode(c *gin.Context) {
|
||||
var req SendCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] SendCode bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] SendCode invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Purpose != "register" && req.Purpose != "reset" {
|
||||
log.Printf("[Auth] SendCode invalid purpose: %s", req.Purpose)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求类型"})
|
||||
return
|
||||
}
|
||||
|
||||
// 注册时检查邮箱是否已注册
|
||||
if req.Purpose == "register" {
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing != nil {
|
||||
log.Printf("[Auth] SendCode email already registered: %s", req.Email)
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 重置密码时检查邮箱是否存在
|
||||
if req.Purpose == "reset" {
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing == nil {
|
||||
log.Printf("[Auth] SendCode email not found for reset: %s", req.Email)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "邮箱未注册"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 限频检查
|
||||
if !h.verify.CheckRateLimit(req.Email, req.Purpose) {
|
||||
log.Printf("[Auth] SendCode rate limit exceeded: %s, purpose: %s", req.Email, req.Purpose)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"error": "发送过于频繁,请 60 秒后重试"})
|
||||
return
|
||||
}
|
||||
|
||||
code := h.verify.GenerateCode(req.Email, req.Purpose)
|
||||
if err := h.email.SendVerificationCode(req.Email, code, req.Purpose); err != nil {
|
||||
log.Printf("[Auth] SendCode send email failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "发送验证码失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
|
||||
}
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func (h *AuthHandler) VerifyCode(c *gin.Context) {
|
||||
var req VerifyCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] VerifyCode bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.verify.VerifyCode(req.Email, req.Code, req.Purpose); err != nil {
|
||||
log.Printf("[Auth] VerifyCode failed: email=%s, purpose=%s, err=%v", req.Email, req.Purpose, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "验证码正确"})
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] Register bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] Register invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已注册
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing != nil {
|
||||
log.Printf("[Auth] Register email already registered: %s", req.Email)
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证并消耗验证码
|
||||
if err := h.verify.UseCode(req.Email, req.Code, "register"); err != nil {
|
||||
log.Printf("[Auth] Register use code failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 哈希密码
|
||||
hashedPassword, err := h.auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register hash password failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user, err := h.db.CreateUser(req.Email, hashedPassword)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register create user failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 token
|
||||
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register generate token failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] Login bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写邮箱和密码"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] Login invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := h.db.GetUserByEmail(req.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Login user not found: %s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !h.auth.CheckPassword(req.Password, user.HashedPassword) {
|
||||
log.Printf("[Auth] Login password mismatch: email=%s", req.Email)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 token
|
||||
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Login generate token failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "登录失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||
var req ResetPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] ResetPassword bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] ResetPassword invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证并消耗验证码
|
||||
if err := h.verify.UseCode(req.Email, req.Code, "reset"); err != nil {
|
||||
log.Printf("[Auth] ResetPassword use code failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := h.db.GetUserByEmail(req.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] ResetPassword user not found: %s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 哈希新密码
|
||||
hashedPassword, err := h.auth.HashPassword(req.NewPassword)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] ResetPassword hash password failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
if err := h.db.UpdatePassword(user.ID, hashedPassword); err != nil {
|
||||
log.Printf("[Auth] ResetPassword update password failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "密码重置成功"})
|
||||
}
|
||||
|
||||
// GetMe 获取当前用户信息
|
||||
func (h *AuthHandler) GetMe(c *gin.Context) {
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
log.Printf("[Auth] GetMe userID not found in context")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.db.GetUserByID(userID.(int64))
|
||||
if err != nil {
|
||||
log.Printf("[Auth] GetMe user not found: userID=%v, err=%v", userID, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// AuthMiddleware JWT 认证中间件
|
||||
func AuthMiddleware(auth *services.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
log.Printf("[Auth] Middleware missing Authorization header: ip=%s", c.ClientIP())
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 提取 Bearer token
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
log.Printf("[Auth] Middleware invalid auth format: ip=%s", c.ClientIP())
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证格式"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := auth.ValidateToken(parts[1])
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Middleware token validation failed: ip=%s, err=%v", c.ClientIP(), err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存入上下文
|
||||
c.Set("userID", payload.UserID)
|
||||
c.Set("email", payload.Email)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user