package handler import ( "encoding/json" "fmt" "io" "net/http" "net/url" "time" "github.com/gin-gonic/gin" "github.com/gouki/lunar-server/internal/config" "github.com/gouki/lunar-server/internal/model" "github.com/gouki/lunar-server/internal/service" ) // code2Session 调用微信 jscode2session 接口换取 openid // session_key 仅保存在服务端,绝不下发给客户端 func code2Session(code string) (openID, unionID string, err error) { cfg := config.Load() if cfg.Wechat.AppID == "" || cfg.Wechat.AppSecret == "" { return "", "", fmt.Errorf("wechat appid/secret not configured") } query := url.Values{ "appid": {cfg.Wechat.AppID}, "secret": {cfg.Wechat.AppSecret}, "js_code": {code}, "grant_type": {"authorization_code"}, } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get("https://api.weixin.qq.com/sns/jscode2session?" + query.Encode()) if err != nil { return "", "", fmt.Errorf("jscode2session request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) if err != nil { return "", "", fmt.Errorf("read jscode2session response failed: %w", err) } var result struct { OpenID string `json:"openid"` UnionID string `json:"unionid"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } if err := json.Unmarshal(body, &result); err != nil { return "", "", fmt.Errorf("parse jscode2session response failed: %w", err) } if result.ErrCode != 0 || result.OpenID == "" { return "", "", fmt.Errorf("wechat auth failed: errcode=%d errmsg=%s", result.ErrCode, result.ErrMsg) } return result.OpenID, result.UnionID, nil } // upsertUser 根据 openid 获取用户,不存在则创建 func upsertUser(openID, unionID string) (*model.User, error) { userService := service.NewUserService() user, err := userService.GetUserByOpenID(openID) if err != nil { return nil, err } if user == nil { user = &model.User{ OpenID: openID, UnionID: unionID, Nickname: "微信用户", Status: 1, } if err := userService.CreateUser(user); err != nil { return nil, err } } return user, nil } // UserLogin 用户登录(小程序 wx.login 的 code 换取 token) func UserLogin(c *gin.Context) { var req struct { Code string `json:"code" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "参数错误", }) return } openID, unionID, err := code2Session(req.Code) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "微信登录失败", }) return } user, err := upsertUser(openID, unionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "创建用户失败", }) return } // 禁用用户不允许登录 if user.Status != 1 { c.JSON(http.StatusForbidden, gin.H{ "code": 403, "msg": "账号已被禁用", }) return } cfg := config.Load() userService := service.NewUserService() token, err := userService.GenerateToken(user.ID, cfg.JWT.Secret) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "生成token失败", }) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "success", "data": gin.H{ "token": token, "user": user, }, }) } // UserLogout 用户登出 func UserLogout(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "success", }) } // GetUserProfile 获取用户资料 func GetUserProfile(c *gin.Context) { userID, exists := c.Get("userID") if !exists { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "未授权", }) return } userService := service.NewUserService() user, err := userService.GetUserByID(userID.(uint)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "获取用户信息失败", }) return } profile, _ := userService.GetUserProfile(user.ID) c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "success", "data": gin.H{ "user": user, "profile": profile, }, }) } // UpdateUserProfile 更新用户资料 func UpdateUserProfile(c *gin.Context) { userID, exists := c.Get("userID") if !exists { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "未授权", }) return } var req struct { Nickname string `json:"nickname"` Avatar string `json:"avatar"` Gender int `json:"gender"` RealName string `json:"realName"` Birthday string `json:"birthday"` BirthTime string `json:"birthTime"` Zodiac string `json:"zodiac"` Constellation string `json:"constellation"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "参数错误", }) return } userService := service.NewUserService() // 更新用户基本信息 user, err := userService.GetUserByID(userID.(uint)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "获取用户信息失败", }) return } if req.Nickname != "" { user.Nickname = req.Nickname } if req.Avatar != "" { user.Avatar = req.Avatar } if req.Gender > 0 { user.Gender = req.Gender } if err := userService.UpdateUser(user); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "更新用户信息失败", }) return } // 更新用户资料 profile, _ := userService.GetUserProfile(user.ID) if profile == nil { profile = &model.UserProfile{ UserID: user.ID, } } if req.RealName != "" { profile.RealName = req.RealName } if req.Birthday != "" { profile.Birthday = req.Birthday } if req.BirthTime != "" { profile.BirthTime = req.BirthTime } if req.Zodiac != "" { profile.Zodiac = req.Zodiac } if req.Constellation != "" { profile.Constellation = req.Constellation } if err := userService.UpdateUserProfile(profile); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "msg": "更新用户资料失败", }) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "success", "data": gin.H{ "user": user, "profile": profile, }, }) } // WechatAuth 微信授权(仅返回 openid,session_key 属敏感凭证不下发) func WechatAuth(c *gin.Context) { var req struct { Code string `json:"code" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "msg": "参数错误", }) return } openID, _, err := code2Session(req.Code) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "code": 401, "msg": "微信授权失败", }) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "success", "data": gin.H{ "openid": openID, }, }) }