package service import ( "errors" "time" "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model" "github.com/silenceper/wechat/v2" "github.com/silenceper/wechat/v2/cache" "github.com/silenceper/wechat/v2/miniprogram" miniConfig "github.com/silenceper/wechat/v2/miniprogram/config" "gorm.io/gorm" ) type MiniService struct{} // GetWechatMiniProgram 获取微信小程序实例 func (w *MiniService) GetWechatMiniProgram() (*miniprogram.MiniProgram, error) { if global.GVA_CONFIG.Wechat.MiniAppID == "" || global.GVA_CONFIG.Wechat.MiniAppSecret == "" { return nil, errors.New("微信小程序配置不完整") } wc := wechat.NewWechat() memory := cache.NewMemory() cfg := &miniConfig.Config{ AppID: global.GVA_CONFIG.Wechat.MiniAppID, AppSecret: global.GVA_CONFIG.Wechat.MiniAppSecret, Cache: memory, } return wc.GetMiniProgram(cfg), nil } // Code2Session 小程序登录凭证校验 func (w *MiniService) Code2Session(code string) (*model.MiniUser, error) { mini, err := w.GetWechatMiniProgram() if err != nil { return nil, err } // 调用微信接口获取 session session, err := mini.GetAuth().Code2Session(code) if err != nil { global.GVA_LOG.Error("微信小程序 Code2Session 失败: " + err.Error()) return nil, err } if session.OpenID == "" { return nil, errors.New("获取用户 OpenID 失败") } // 查找或创建用户 var user model.MiniUser err = global.GVA_DB.Where("open_id = ?", session.OpenID).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // 创建新用户 // 生成默认昵称:萌宠爱 + OpenID的前6位 defaultNickname := "萌宠爱" + session.OpenID[:6] user = model.MiniUser{ OpenID: session.OpenID, SessionKey: &session.SessionKey, Nickname: &defaultNickname, } if session.UnionID != "" { user.UnionID = &session.UnionID } now := time.Now() user.LastLoginTime = &now err = global.GVA_DB.Create(&user).Error if err != nil { global.GVA_LOG.Error("创建微信小程序用户失败: " + err.Error()) return nil, err } } else { global.GVA_LOG.Error("查询微信小程序用户失败: " + err.Error()) return nil, err } } else { // 更新用户信息 now := time.Now() user.SessionKey = &session.SessionKey user.LastLoginTime = &now if session.UnionID != "" { user.UnionID = &session.UnionID } err = global.GVA_DB.Save(&user).Error if err != nil { global.GVA_LOG.Error("更新微信小程序用户失败: " + err.Error()) return nil, err } } return &user, nil } // GetUserInfo 获取用户信息 func (w *MiniService) GetUserInfo(openid string) (*model.MiniUser, error) { var user model.MiniUser err := global.GVA_DB.Where("open_id = ?", openid).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户不存在") } return nil, err } return &user, nil } // GetUserByID 根据用户ID获取用户信息 func (w *MiniService) GetUserByID(userID uint) (*model.MiniUser, error) { var user model.MiniUser err := global.GVA_DB.Where("id = ?", userID).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户不存在") } return nil, err } return &user, nil } // UpdateUserInfo 更新用户信息 func (w *MiniService) UpdateUserInfo(openid string, userInfo map[string]interface{}) error { updates := make(map[string]interface{}) if nickname, ok := userInfo["nickname"]; ok { if str, ok := nickname.(string); ok && str != "" { updates["nickname"] = str } } if avatarUrl, ok := userInfo["avatarUrl"]; ok { if str, ok := avatarUrl.(string); ok && str != "" { updates["avatar_url"] = str } } if gender, ok := userInfo["gender"]; ok { if num, ok := gender.(float64); ok { updates["gender"] = int(num) } } if country, ok := userInfo["country"]; ok { if str, ok := country.(string); ok && str != "" { updates["country"] = str } } if province, ok := userInfo["province"]; ok { if str, ok := province.(string); ok && str != "" { updates["province"] = str } } if city, ok := userInfo["city"]; ok { if str, ok := city.(string); ok && str != "" { updates["city"] = str } } if language, ok := userInfo["language"]; ok { if str, ok := language.(string); ok && str != "" { updates["language"] = str } } if len(updates) == 0 { return errors.New("没有需要更新的信息") } err := global.GVA_DB.Model(&model.MiniUser{}).Where("open_id = ?", openid).Updates(updates).Error if err != nil { global.GVA_LOG.Error("更新微信小程序用户信息失败: " + err.Error()) return err } return nil } // GetUserList 获取用户列表 func (w *MiniService) GetUserList(page, pageSize int) ([]model.MiniUser, int64, error) { var users []model.MiniUser var total int64 db := global.GVA_DB.Model(&model.MiniUser{}) // 获取总数 err := db.Count(&total).Error if err != nil { return nil, 0, err } // 分页查询 offset := (page - 1) * pageSize err = db.Offset(offset).Limit(pageSize).Order("created_at desc").Find(&users).Error if err != nil { return nil, 0, err } return users, total, nil } // GetUserByUnionID 根据 UnionID 获取用户信息 func (w *MiniService) GetUserByUnionID(unionid string) (*model.MiniUser, error) { var user model.MiniUser err := global.GVA_DB.Where("unionid = ?", unionid).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户不存在") } return nil, err } return &user, nil } // BindPhone 绑定手机号 func (w *MiniService) BindPhone(openid, phone string) (*model.MiniUser, error) { // 更新小程序用户的手机号 err := global.GVA_DB.Model(&model.MiniUser{}).Where("open_id = ?", openid).Update("phone", phone).Error if err != nil { global.GVA_LOG.Error("更新手机号失败: " + err.Error()) return nil, err } // 获取更新后的用户信息 var user model.MiniUser err = global.GVA_DB.Where("open_id = ?", openid).First(&user).Error if err != nil { return nil, err } global.GVA_LOG.Info("用户绑定手机号成功: " + phone + ", openid: " + openid) return &user, nil } // GetUserByPhone 根据手机号获取用户 func (w *MiniService) GetUserByPhone(phone string) (*model.MiniUser, error) { var user model.MiniUser err := global.GVA_DB.Where("phone = ?", phone).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("用户不存在") } return nil, err } return &user, nil } // CheckUnionIDExists 检查 UnionID 是否已存在(为APP登录预留) func (w *MiniService) CheckUnionIDExists(unionid string) (*model.MiniUser, error) { if unionid == "" { return nil, errors.New("unionid 不能为空") } var user model.MiniUser err := global.GVA_DB.Where("union_id = ?", unionid).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil // 不存在,返回 nil 但不报错 } return nil, err } return &user, nil } // UpdatePhoneByEncryption 通过加密数据更新用户手机号 func (w *MiniService) UpdatePhoneByEncryption(openid, encryptedData, iv string) (string, error) { // 1. 获取用户信息 var user model.MiniUser err := global.GVA_DB.Where("open_id = ?", openid).First(&user).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return "", errors.New("用户不存在") } return "", err } if user.SessionKey == nil || *user.SessionKey == "" { return "", errors.New("用户 SessionKey 不存在,请重新登录") } // 2. 获取微信小程序实例 mini, err := w.GetWechatMiniProgram() if err != nil { return "", err } // 3. 使用 session_key 解密手机号 encryptor := mini.GetEncryptor() plainData, err := encryptor.Decrypt(*user.SessionKey, encryptedData, iv) if err != nil { global.GVA_LOG.Error("解密手机号失败: " + err.Error()) return "", errors.New("解密手机号失败") } // 4. 从解密数据中获取手机号 phoneNumber := "" if plainData.PhoneNumber != "" { phoneNumber = plainData.PhoneNumber } else { return "", errors.New("解密数据中未找到手机号") } // 5. 更新用户手机号 err = global.GVA_DB.Model(&user).Update("phone", phoneNumber).Error if err != nil { global.GVA_LOG.Error("更新手机号失败: " + err.Error()) return "", err } global.GVA_LOG.Info("用户手机号更新成功: " + phoneNumber + ", openid: " + openid) return phoneNumber, nil }