package service import ( "dianshang/internal/model" "dianshang/internal/repository" "dianshang/pkg/jwt" "encoding/json" "errors" "fmt" "io" "math/rand" "net/http" "time" "gorm.io/gorm" ) // WeChatService 微信服务 type WeChatService struct { userRepo *repository.UserRepository pointsService *PointsService db *gorm.DB appID string appSecret string } // NewWeChatService 创建微信服务实例 func NewWeChatService(db *gorm.DB, pointsService *PointsService, appID, appSecret string) *WeChatService { // 初始化随机数种子 rand.Seed(time.Now().UnixNano()) return &WeChatService{ userRepo: repository.NewUserRepository(db), pointsService: pointsService, db: db, appID: appID, appSecret: appSecret, } } // WeChatLoginResponse 微信登录响应 type WeChatLoginResponse struct { OpenID string `json:"openid"` SessionKey string `json:"session_key"` UnionID string `json:"unionid"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } // WeChatUserInfo 微信用户信息 type WeChatUserInfo struct { OpenID string `json:"openId"` NickName string `json:"nickName"` Gender int `json:"gender"` City string `json:"city"` Province string `json:"province"` Country string `json:"country"` AvatarURL string `json:"avatarUrl"` Language string `json:"language"` } // Login 微信登录 func (s *WeChatService) Login(code string, ip string, userAgent string) (*model.User, string, error) { // 验证输入参数 if code == "" { return nil, "", errors.New("微信登录code不能为空") } fmt.Printf("开始微信登录流程: code=%s\n", code) // 1. 调用微信API获取openid和session_key wechatResp, err := s.getWeChatSession(code) if err != nil { s.logUserLogin(0, "wechat", false, fmt.Sprintf("获取微信会话失败: %v", err), ip, userAgent) return nil, "", fmt.Errorf("获取微信会话失败: %v", err) } if wechatResp.ErrCode != 0 { errorMsg := fmt.Sprintf("微信API返回错误: code=%d, msg=%s", wechatResp.ErrCode, wechatResp.ErrMsg) s.logUserLogin(0, "wechat", false, errorMsg, ip, userAgent) return nil, "", fmt.Errorf("微信登录失败: %s", wechatResp.ErrMsg) } fmt.Printf("成功获取微信会话: OpenID=%s\n", wechatResp.OpenID) // 2. 查找或创建用户 user, err := s.findOrCreateUser(wechatResp) if err != nil { s.logUserLogin(0, "wechat", false, fmt.Sprintf("用户处理失败: %v", err), ip, userAgent) return nil, "", fmt.Errorf("用户处理失败: %v", err) } // 3. 保存微信会话信息 if err := s.saveWeChatSession(user.ID, wechatResp); err != nil { s.logUserLogin(user.ID, "wechat", false, fmt.Sprintf("保存会话失败: %v", err), ip, userAgent) return nil, "", fmt.Errorf("保存会话失败: %v", err) } // 4. 生成自定义登录态(JWT token) // 按照微信官方建议,生成自定义登录态用于维护用户登录状态 tokenExpiry := 7 * 24 * 3600 // 7天有效期,与session_key保持一致 token, err := jwt.GenerateToken(user.ID, "user", tokenExpiry) if err != nil { s.logUserLogin(user.ID, "wechat", false, fmt.Sprintf("生成token失败: %v", err), ip, userAgent) return nil, "", fmt.Errorf("生成自定义登录态失败: %v", err) } // 5. 检查并给予每日首次登录积分 if s.pointsService != nil { awarded, err := s.pointsService.CheckAndGiveDailyLoginPoints(user.ID) if err != nil { fmt.Printf("每日登录积分处理失败: %v\n", err) } else if awarded { fmt.Printf("用户 %d 获得每日首次登录积分\n", user.ID) } } // 6. 记录登录日志 s.logUserLogin(user.ID, "wechat", true, "", ip, userAgent) fmt.Printf("微信登录成功: UserID=%d, OpenID=%s, Token生成完成\n", user.ID, user.OpenID) return user, token, nil } // LoginWithUserInfo 微信登录并更新用户信息 func (s *WeChatService) LoginWithUserInfo(code string, userInfo WeChatUserInfo, ip string, userAgent string) (*model.User, string, error) { // 1. 先进行基本登录 user, token, err := s.Login(code, ip, userAgent) if err != nil { return nil, "", err } // 2. 更新用户信息 if err := s.updateUserInfo(user.ID, userInfo); err != nil { return nil, "", fmt.Errorf("更新用户信息失败: %v", err) } // 3. 重新获取用户信息 updatedUser, err := s.userRepo.GetByID(user.ID) if err != nil { return nil, "", fmt.Errorf("获取用户信息失败: %v", err) } return updatedUser, token, nil } // getWeChatSession 获取微信会话(按照官方文档标准实现code2Session) func (s *WeChatService) getWeChatSession(code string) (*WeChatLoginResponse, error) { // 验证code格式 if code == "" { return nil, errors.New("登录凭证code不能为空") } if len(code) < 10 { return nil, errors.New("登录凭证code格式异常") } // 开发模式:如果AppSecret是占位符或为空,返回模拟数据 // 注意:当配置了真实的AppSecret时,会调用微信官方API if s.appSecret == "your-wechat-app-secret" || s.appSecret == "your_wechat_appsecret" || s.appSecret == "" { // 在开发模式下,使用固定的OpenID来模拟同一个微信用户 // 这样可以避免每次登录都创建新用户的问题 return &WeChatLoginResponse{ OpenID: "dev_openid_fixed_user_001", // 使用固定的OpenID SessionKey: "dev_session_key_" + time.Now().Format("20060102150405"), UnionID: "dev_unionid_fixed_user_001", // 使用固定的UnionID ErrCode: 0, ErrMsg: "", }, nil } // 按照微信官方文档调用auth.code2Session接口 url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", s.appID, s.appSecret, code) // 创建HTTP客户端,设置超时 client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("调用微信API失败: %v", err) } defer resp.Body.Close() // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("微信API返回异常状态码: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取微信API响应失败: %v", err) } var wechatResp WeChatLoginResponse if err := json.Unmarshal(body, &wechatResp); err != nil { return nil, fmt.Errorf("解析微信API响应失败: %v", err) } // 检查微信API返回的错误 if wechatResp.ErrCode != 0 { return nil, fmt.Errorf("微信API错误 [%d]: %s", wechatResp.ErrCode, wechatResp.ErrMsg) } // 验证必要字段 if wechatResp.OpenID == "" { return nil, errors.New("微信API未返回OpenID") } if wechatResp.SessionKey == "" { return nil, errors.New("微信API未返回SessionKey") } return &wechatResp, nil } // generateRandomUsername 生成随机用户名,格式为"用户xxxxxxxx"(包含字母和数字) func (s *WeChatService) generateRandomUsername() string { // 定义字符集:数字和小写字母 charset := "0123456789abcdefghijklmnopqrstuvwxyz" // 生成8位随机字符串 randomSuffix := make([]byte, 8) for i := range randomSuffix { randomSuffix[i] = charset[rand.Intn(len(charset))] } return fmt.Sprintf("用户%s", string(randomSuffix)) } // findOrCreateUser 查找或创建用户 func (s *WeChatService) findOrCreateUser(wechatResp *WeChatLoginResponse) (*model.User, error) { // 验证必要参数 if wechatResp.OpenID == "" { return nil, errors.New("微信OpenID不能为空") } // 先尝试通过openid查找用户 user, err := s.userRepo.GetByOpenID(wechatResp.OpenID) if err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("查询用户失败: %v", err) } } else { // 用户已存在,检查状态 if user.Status == 0 { return nil, errors.New("用户已被禁用,请联系客服") } fmt.Printf("找到已存在用户: ID=%d, OpenID=%s, Nickname=%s\n", user.ID, user.OpenID, user.Nickname) return user, nil } // 用户不存在,创建新用户 fmt.Printf("用户不存在,开始创建新用户: OpenID=%s\n", wechatResp.OpenID) // 生成随机用户名,格式为"用户xxxxxxxx" randomUsername := s.generateRandomUsername() user = &model.User{ OpenID: wechatResp.OpenID, UnionID: wechatResp.UnionID, Nickname: randomUsername, Avatar: "", // 默认头像为空,后续可通过授权获取 Status: 1, // 1表示正常状态 Level: 1, // 初始等级为1 Gender: 0, // 0表示未知性别 } if err := s.userRepo.Create(user); err != nil { return nil, fmt.Errorf("创建用户失败: %v", err) } fmt.Printf("成功创建新用户: ID=%d, OpenID=%s, Nickname=%s\n", user.ID, user.OpenID, user.Nickname) return user, nil } // saveWeChatSession 保存微信会话信息(安全存储session_key) func (s *WeChatService) saveWeChatSession(userID uint, wechatResp *WeChatLoginResponse) error { // session_key是敏感信息,需要安全存储 // 在生产环境中,建议对session_key进行加密存储 // 计算session_key过期时间(微信session_key有效期通常为7天) sessionExpiry := time.Now().Add(7 * 24 * time.Hour) // 简单示例:保存到用户表的额外字段中 // 在生产环境中,建议使用专门的会话表或Redis等缓存存储 updates := map[string]interface{}{ "open_id": wechatResp.OpenID, "wechat_session_key": wechatResp.SessionKey, // 生产环境中应加密存储 "union_id": wechatResp.UnionID, "session_expiry": sessionExpiry, "updated_at": time.Now(), } if err := s.db.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil { return fmt.Errorf("保存微信会话信息失败: %v", err) } // 记录会话创建日志 fmt.Printf("用户 %d 的微信会话已保存,OpenID: %s, 过期时间: %s\n", userID, wechatResp.OpenID, sessionExpiry.Format("2006-01-02 15:04:05")) return nil } // updateUserInfo 更新用户信息 func (s *WeChatService) updateUserInfo(userID uint, userInfo WeChatUserInfo) error { updates := map[string]interface{}{ "nickname": userInfo.NickName, "avatar": userInfo.AvatarURL, "gender": userInfo.Gender, } if err := s.userRepo.Update(userID, updates); err != nil { return err } // 获取用户的openid(从ai_users表中获取) user, err := s.userRepo.GetByID(userID) if err != nil { return fmt.Errorf("获取用户信息失败: %v", err) } // 保存详细的微信用户信息 wechatUserInfo := struct { ID uint `gorm:"primaryKey"` UserID uint `gorm:"column:user_id;not null;unique"` OpenID string `gorm:"column:openid;not null;unique"` Nickname string `gorm:"column:nickname"` AvatarURL string `gorm:"column:avatar_url"` Gender int `gorm:"column:gender"` Country string `gorm:"column:country"` Province string `gorm:"column:province"` City string `gorm:"column:city"` Language string `gorm:"column:language"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` }{ UserID: userID, OpenID: user.OpenID, // 使用从数据库获取的openid Nickname: userInfo.NickName, AvatarURL: userInfo.AvatarURL, Gender: userInfo.Gender, Country: userInfo.Country, Province: userInfo.Province, City: userInfo.City, Language: userInfo.Language, } return s.db.Table("ai_wechat_user_info").Save(&wechatUserInfo).Error } // logUserLogin 记录用户登录日志 func (s *WeChatService) logUserLogin(userID uint, loginType string, success bool, errorMsg string, ip string, userAgent string) { status := 1 if !success { status = 0 } // 使用LogService创建登录日志 logService := NewLogService(s.db) remark := loginType if errorMsg != "" { remark = fmt.Sprintf("%s: %s", loginType, errorMsg) } err := logService.CreateLoginLog(userID, ip, userAgent, status, remark) if err != nil { fmt.Printf("创建登录日志失败: %v\n", err) } } // GetUserSession 获取用户会话信息 func (s *WeChatService) GetUserSession(userID uint) (map[string]interface{}, error) { var user model.User err := s.db.Where("id = ?", userID).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户不存在") } return nil, fmt.Errorf("查询用户失败: %v", err) } // 检查session是否过期 if user.SessionExpiry != nil && user.SessionExpiry.Before(time.Now()) { return nil, errors.New("会话已过期") } return map[string]interface{}{ "session_key": user.WeChatSessionKey, "openid": user.OpenID, "unionid": user.UnionID, "expires_at": user.SessionExpiry, }, nil } // ValidateSessionKey 验证session_key有效性 func (s *WeChatService) ValidateSessionKey(userID uint) (bool, error) { session, err := s.GetUserSession(userID) if err != nil { return false, err } // 检查session_key是否存在 sessionKey, ok := session["session_key"].(string) if !ok || sessionKey == "" { return false, errors.New("session_key不存在") } // 检查过期时间 expiresAt, ok := session["expires_at"].(*time.Time) if ok && expiresAt != nil && expiresAt.Before(time.Now()) { return false, errors.New("session_key已过期") } return true, nil }