package handlers import ( "database/sql" "encoding/json" "errors" "net/http" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" "mmgame/internal/config" "mmgame/internal/database" "mmgame/internal/models" "mmgame/internal/util" ) var errUnauthorized = errors.New("unauthorized") func createToken(userID string) (string, error) { exp := time.Now().Add(time.Duration(config.Cfg.AccessTokenExpireMinutes) * time.Minute) claims := jwt.MapClaims{ "sub": userID, "exp": exp.Unix(), "v": 0, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(config.Cfg.SecretKey)) } func currentUser(r *http.Request) (*models.User, error) { auth := r.Header.Get("Authorization") if !strings.HasPrefix(auth, "Bearer ") { return nil, errUnauthorized } tokenStr := strings.TrimPrefix(auth, "Bearer ") claims := jwt.MapClaims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { return []byte(config.Cfg.SecretKey), nil }, jwt.WithValidMethods([]string{config.Cfg.Algorithm})) if err != nil || !token.Valid { return nil, errUnauthorized } userID, _ := claims["sub"].(string) if userID == "" { return nil, errUnauthorized } row := database.DB.QueryRow( `SELECT id, email, nickname, avatar_url, password_hash, role, is_guest, game_count, token_version, created_at FROM users WHERE id = ?`, userID) var u models.User var email, nickname, avatarURL, passwordHash, createdAt sql.NullString if err := row.Scan(&u.ID, &email, &nickname, &avatarURL, &passwordHash, &u.Role, &u.IsGuest, &u.GameCount, &u.TokenVersion, &createdAt); err != nil { return nil, errUnauthorized } if email.Valid { u.Email = &email.String } if nickname.Valid { u.Nickname = &nickname.String } if avatarURL.Valid { u.AvatarURL = &avatarURL.String } if passwordHash.Valid { u.PasswordHash = &passwordHash.String } u.CreatedAt = util.ParseTime(createdAt.String) return &u, nil } func Register(w http.ResponseWriter, r *http.Request) { var req struct { Email string `json:"email"` Password string `json:"password"` Nickname string `json:"nickname"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } var existingID string err := database.DB.QueryRow(`SELECT id FROM users WHERE email = ?`, req.Email).Scan(&existingID) if err == nil { writeError(w, 400, "邮箱已注册") return } if err != sql.ErrNoRows { writeError(w, 500, "服务器错误") return } hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { writeError(w, 500, "服务器错误") return } userID := newID() _, err = database.DB.Exec( `INSERT INTO users (id, email, nickname, password_hash, role, is_guest, game_count, token_version, created_at) VALUES (?, ?, ?, ?, ?, 0, 0, 0, ?)`, userID, req.Email, req.Nickname, string(hash), models.RolePlayer, util.NowStr()) if err != nil { writeError(w, 500, "服务器错误") return } token, _ := createToken(userID) writeJSON(w, 200, map[string]interface{}{ "token": token, "user_id": userID, "nickname": req.Nickname, "is_guest": false, }) } func Login(w http.ResponseWriter, r *http.Request) { var req struct { Email string `json:"email"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, 422, "请求格式错误") return } var id, email, nickname, passwordHash string err := database.DB.QueryRow( `SELECT id, email, nickname, password_hash FROM users WHERE email = ?`, req.Email). Scan(&id, &email, &nickname, &passwordHash) if err == sql.ErrNoRows || passwordHash == "" { writeError(w, 401, "邮箱或密码错误") return } if err != nil { writeError(w, 500, "服务器错误") return } if bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)) != nil { writeError(w, 401, "邮箱或密码错误") return } token, _ := createToken(id) writeJSON(w, 200, map[string]interface{}{ "token": token, "user_id": id, "nickname": nickname, "is_guest": false, }) } func GuestLogin(w http.ResponseWriter, r *http.Request) { var req struct { Nickname string `json:"nickname"` } _ = json.NewDecoder(r.Body).Decode(&req) if req.Nickname == "" { req.Nickname = "游客" } userID := newID() _, err := database.DB.Exec( `INSERT INTO users (id, nickname, role, is_guest, game_count, token_version, created_at) VALUES (?, ?, ?, 1, 0, 0, ?)`, userID, req.Nickname, models.RolePlayer, util.NowStr()) if err != nil { writeError(w, 500, "服务器错误") return } token, _ := createToken(userID) writeJSON(w, 200, map[string]interface{}{ "token": token, "user_id": userID, "nickname": req.Nickname, "is_guest": true, }) } func Logout(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "ok"}) } func GetProfile(w http.ResponseWriter, r *http.Request) { user, err := currentUser(r) if err != nil { writeError(w, 401, "未登录") return } writeJSON(w, 200, map[string]interface{}{ "user_id": user.ID, "email": nullStr(user.Email), "nickname": nullStr(user.Nickname), "avatar_url": nullStr(user.AvatarURL), "is_guest": user.IsGuest, "role": user.Role, "game_count": user.GameCount, "created_at": util.TimeStr(user.CreatedAt), }) } func UpdateProfile(w http.ResponseWriter, r *http.Request) { user, err := currentUser(r) if err != nil { writeError(w, 401, "未登录") return } var data map[string]interface{} _ = json.NewDecoder(r.Body).Decode(&data) var nickname, avatarURL *string if v, ok := data["nickname"].(string); ok { nickname = &v } if v, ok := data["avatar_url"].(string); ok { avatarURL = &v } if nickname != nil { if _, err := database.DB.Exec(`UPDATE users SET nickname = ? WHERE id = ?`, *nickname, user.ID); err != nil { writeError(w, 500, "服务器错误") return } } if avatarURL != nil { if _, err := database.DB.Exec(`UPDATE users SET avatar_url = ? WHERE id = ?`, *avatarURL, user.ID); err != nil { writeError(w, 500, "服务器错误") return } } writeJSON(w, 200, map[string]string{"status": "ok"}) } func GetHistory(w http.ResponseWriter, r *http.Request) { if _, err := currentUser(r); err != nil { writeError(w, 401, "未登录") return } rows, err := database.DB.Query( `SELECT id, script_id, status, phase, phase_index, started_at, completed_at, created_at FROM game_sessions ORDER BY created_at DESC LIMIT 50`) if err != nil { writeError(w, 500, "服务器错误") return } defer rows.Close() var items []map[string]interface{} for rows.Next() { var id, scriptID, status, phase, startedAt, completedAt, createdAt sql.NullString var phaseIndex int if err := rows.Scan(&id, &scriptID, &status, &phase, &phaseIndex, &startedAt, &completedAt, &createdAt); err != nil { continue } items = append(items, map[string]interface{}{ "id": id.String, "script_id": scriptID.String, "status": status.String, "phase": phase.String, "phase_index": phaseIndex, "started_at": startedAt.String, "completed_at": completedAt.String, "created_at": createdAt.String, }) } writeJSON(w, 200, items) }