package internal import ( "context" "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" "server/internal/consts" "server/internal/dao" "server/internal/model" "server/internal/model/do" "server/internal/model/entity" "server/internal/service" "server/utility" "server/utility/ecode" "server/utility/encrypt" "server/utility/gamelife" "server/utility/jwt" "server/utility/sms" "strings" ) type sUser struct{} func init() { service.RegisterUser(New()) go checkUserRole() } func checkUserRole() { ctx := context.Background() exist, err := dao.Roles.Ctx(ctx).Where(do.Roles{Code: consts.UserRoleCode}).Exist() if err != nil { return } if !exist { _, err := dao.Roles.Ctx(ctx).Data(do.Roles{ Name: "用户", Code: consts.UserRoleCode, Description: "用户角色", Status: consts.RoleEnable, IsDeletable: false, }).Insert() if err != nil { return } } } func New() service.IUser { return &sUser{} } func (s *sUser) Login(ctx context.Context, in *model.UserLoginIn) (out *model.UserLoginOut, err error) { // Fetch role information value, err := dao.Roles.Ctx(ctx).Where(do.Roles{Code: consts.UserRoleCode}).Fields(dao.Roles.Columns().Code, dao.Roles.Columns().Id).One() if err != nil { return nil, ecode.Fail.Sub("查找角色失败") } split := strings.Split(in.SceneId, "_") if len(split) < 3 { return nil, ecode.Fail.Sub("SceneId格式错误") } storeId := gconv.Int64(split[0]) var xyUserId string var userId int64 if split[1] == "0" { // Case 1: split[1] == "0", use in.OpenId to find or create user exist, err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId}).Exist() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } if !exist { // User does not exist, create new user var username string for { randomStr := grand.Str("abcdefghijklmnopqrstuvwxyz0123456789", 8) username = "qy_" + randomStr count, err := dao.Users.Ctx(ctx).Where(do.Users{Username: username}).Count() if err != nil { return nil, ecode.Fail.Sub("检查用户名失败") } if count == 0 { break // Username is unique } } password, err := encrypt.EncryptPassword(consts.DefaultPassword) if err != nil { return nil, ecode.Fail.Sub("加密密码失败") } user := &entity.Users{ WxOpenId: in.OpenId, Username: username, Nickname: username, PasswordHash: password, Avatar: consts.DefaultUserAvatar, FirstVisitAt: gtime.Now(), LastLoginAt: gtime.Now(), WxPopenId: utility.GenerateUserID("WX"), QqPopenId: utility.GenerateUserID("QQ"), RoleId: value[dao.Roles.Columns().Id].Int64(), LastLoginStoreId: storeId, } result, err := dao.Users.Ctx(ctx).Insert(user) if err != nil { return nil, ecode.Fail.Sub("创建用户失败") } userId, err = result.LastInsertId() if err != nil { return nil, ecode.Fail.Sub("获取用户ID失败") } } else { // User exists, update last login store ID and time var user entity.Users if err := dao.Users.Ctx(ctx).Where(do.Users{WxOpenId: in.OpenId}).Scan(&user); err != nil { return nil, ecode.Fail.Sub("查找用户失败") } userId = user.Id if _, err := dao.Users.Ctx(ctx).Where(do.Users{Id: userId}).Update(do.Users{ LastLoginAt: gtime.Now(), LastLoginStoreId: storeId, }); err != nil { return nil, ecode.Fail.Sub("更新登录时间失败") } } } else if split[1] == "1" { // Case 2: split[1] == "1", use xyUserId to find user and bind in.OpenId xyUserId = split[2] var user entity.Users exist, err := dao.Users.Ctx(ctx).Where(do.Users{XyUserId: xyUserId}).Exist() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } if !exist { return nil, ecode.Fail.Sub("参数错误:用户不存在") } // User exists, check WxOpenId if err := dao.Users.Ctx(ctx).Where(do.Users{XyUserId: xyUserId}).Scan(&user); err != nil { return nil, ecode.Fail.Sub("查找用户失败") } userId = user.Id if user.WxOpenId != "" && user.WxOpenId != in.OpenId { return nil, ecode.Fail.Sub("当前微信已绑定其他微信号") } // Bind in.OpenId and update last login store ID if _, err := dao.Users.Ctx(ctx).Where(do.Users{Id: userId}).Update(do.Users{ WxOpenId: in.OpenId, LastLoginAt: gtime.Now(), LastLoginStoreId: storeId, }); err != nil { return nil, ecode.Fail.Sub("绑定OpenId失败") } } else { return nil, ecode.Fail.Sub("无效的SceneId类型") } // Generate token token, err := jwt.GenerateToken(&jwt.TokenIn{ UserId: userId, Role: value[dao.Roles.Columns().Code].String(), }) if err != nil { return nil, ecode.Fail.Sub("生成token失败") } out = &model.UserLoginOut{ Token: token, } return out, nil } func (s *sUser) WeChatLogin(ctx context.Context, in *model.WeChatLogin) (out *model.WeChatLoginOut, err error) { return } func (s *sUser) Info(ctx context.Context, in *model.UserInfoIn) (out *model.UserInfoOut, err error) { exist, err := dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).OmitEmptyWhere().Exist() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } if !exist { return nil, ecode.Params.Sub("用户不存在") } var user entity.Users if err := dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).OmitEmptyWhere().Scan(&user); err != nil { return nil, ecode.Fail.Sub("查找用户失败") } out = &model.UserInfoOut{ Id: user.Id, WxOpenId: user.WxOpenId, Username: user.Username, Nickname: user.Nickname, Avatar: user.Avatar, PhoneNumber: user.PhoneNumber, WxPopenId: user.WxPopenId, QQPopenId: user.QqPopenId, RoleId: user.RoleId, } return out, nil } func (s *sUser) Code(ctx context.Context, in *model.GetPhoneCodeIn) (out *model.GetPhoneCodeOut, err error) { // 限制 1min只能调用一次 get, err := g.Redis().Get(ctx, fmt.Sprintf(consts.UserBindPhoneLimitKey, in.Phone)) if err != nil { glog.Errorf(ctx, "Redis 获取绑定手机限制失败: %v", err) return nil, ecode.Fail.Sub("Redis 获取绑定手机限制失败") } if get.IsEmpty() { err = g.Redis().SetEX(ctx, fmt.Sprintf(consts.UserBindPhoneLimitKey, in.Phone), "1", consts.UserCodeExpire) if err != nil { glog.Errorf(ctx, "Redis 设置绑定手机限制失败: %v", err) return nil, ecode.Fail.Sub("Redis 设置绑定手机限制失败") } } else { return nil, ecode.InvalidOperation.Sub("请勿重复请求绑定手机验证码") } code := grand.Digits(6) // 存入 redis err = g.Redis().SetEX(ctx, fmt.Sprintf(consts.UserBindPhoneKey, in.Phone), code, consts.UserCodeExpire) if err != nil { return nil, ecode.Fail.Sub("设置验证码失败") } client, ok := sms.GetClient("aliyunsms") if !ok { return nil, ecode.Fail.Sub("未配置短信平台") } _, err = client.SendCode(ctx, &model.SMSCodeIn{ Code: code, Phone: in.Phone, }) if err != nil { return nil, ecode.Fail.Sub("发送验证码失败") } return &model.GetPhoneCodeOut{ Success: true, }, nil } func (s *sUser) Update(ctx context.Context, in *model.UserUpdateIn) (out *model.UpdateOut, err error) { exist, err := dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).Exist() if err != nil { return nil, ecode.Fail.Sub("该用户不存在") } if !exist { return nil, ecode.Params.Sub("用户不存在") } _, err = dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).Update(do.Users{ Nickname: in.Nickname, Avatar: in.Avatar, }) if err != nil { return nil, ecode.Fail.Sub("更新用户信息失败") } return &model.UpdateOut{ Success: true, }, nil } func (s *sUser) BindPhone(ctx context.Context, in *model.UserBindPhoneIn) (out *model.UserBindPhoneOut, err error) { // 绑定手机号,需要验证入参和缓存中的验证码时候相同 value, err := g.Redis().Get(ctx, fmt.Sprintf(consts.UserBindPhoneKey, in.Phone)) if err != nil { return nil, ecode.Fail.Sub("获取失败") } if value.IsEmpty() { return nil, ecode.Fail.Sub("验证码已过期") } if value.String() != in.PhoneCode { return nil, ecode.Fail.Sub("验证码错误") } _, err = dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).Update(do.Users{ PhoneNumber: in.Phone, }) if err != nil { return nil, ecode.Fail.Sub("绑定手机号失败") } return &model.UserBindPhoneOut{ Success: true, }, nil } func (s *sUser) List(ctx context.Context, in *model.UserListIn) (out *model.UserListOut, err error) { list := make([]model.User, 0) var total int orm := dao.Users.Ctx(ctx) if in.Nickname != "" { orm = orm.WhereLike(dao.Users.Columns().Nickname, "%"+in.Nickname+"%") } if err = orm.Page(in.Page, in.Size).LeftJoin( dao.Stores.Table(), fmt.Sprintf("%s.%s = %s.%s", dao.Users.Table(), dao.Users.Columns().LastLoginStoreId, dao.Stores.Table(), dao.Stores.Columns().Id), ).Fields(fmt.Sprintf("%s.*, %s.%s %s", dao.Users.Table(), dao.Stores.Table(), dao.Stores.Columns().Name, "last_login_store_name")).ScanAndCount(&list, &total, false); err != nil { return nil, ecode.Fail.Sub("获取用户列表失败") } return &model.UserListOut{ List: list, Total: total, }, nil } func (s *sUser) BoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserBoundUrlOut, err error) { url, err := gamelife.GetGamelifeClient(ctx).GetUrl(ctx, in.PopenId, in.AppName, "", in.BindType, in.IsBound) if err != nil { return nil, ecode.Fail.Sub("获取绑定链接失败") } return &model.UserBoundUrlOut{ Url: url, }, nil } func (s *sUser) UnBoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserUnBoundUrlOut, err error) { url, err := gamelife.GetGamelifeClient(ctx).GetUrl(ctx, in.PopenId, in.AppName, in.Nickname, in.BindType, in.IsBound) if err != nil { return nil, ecode.Fail.Sub("获取绑定链接失败") } return &model.UserUnBoundUrlOut{ Url: url, }, nil } func (s *sUser) BoundInfo(ctx context.Context, in *model.UserBoundInfoIn) (out *model.UserBoundInfoOut, err error) { result, err := gamelife.GetGamelifeClient(ctx).GetBound(ctx, in.PopenId) if err != nil { return nil, err } return &model.UserBoundInfoOut{ IsBound: result.Result, AppNames: result.AppNames, Ctime: result.Ctime, Nick: result.Nick, Utype: result.Utype, }, nil } func (s *sUser) DelUser(ctx context.Context, in *model.DelUserIn) (out *model.DeleteOut, err error) { exist, err := dao.Users.Ctx(ctx).Where(do.Users{Id: in.Id}).Exist() if err != nil { return nil, ecode.Fail.Sub("查询该用户失败") } if !exist { return nil, ecode.Params.Sub("该用户不存在") } _, err = dao.Users.Ctx(ctx).Delete(do.Users{Id: in.Id}) if err != nil { return nil, ecode.Fail.Sub("删除用户失败") } return &model.DeleteOut{Success: true}, nil } func (s *sUser) GetUserGameRole(ctx context.Context, in *model.GetUserGameRoleIn) (out *model.GetUserGameRoleOut, err error) { activity, err := gamelife.GetGamelifeClient(ctx).RequestActivity(ctx, &model.QQNetbarActivityIn{ServiceName: consts.QueryUserRoleList, PopenId: in.PopenId, BindType: in.BindType, UserRoleParam: model.UserRoleParam{Gid: in.GameId, Area: in.Area}}) if err != nil { return nil, err } result, ok := activity.(*[]model.UserRole) if !ok { return nil, ecode.Fail.Sub("获取用户游戏角色失败") } return &model.GetUserGameRoleOut{ RoleList: *result, }, nil } func (s *sUser) GamelifePackUrl(ctx context.Context, in *model.GamelifePackUrlIn) (out *model.GamelifePackUrlOut, err error) { url, err := gamelife.GetGamelifeClient(ctx).GetGamelifePackageUrl(ctx, in.PopenId, in.GameCode, in.GameId, in.BindType) if err != nil { return nil, ecode.Fail.Sub("获取绑定链接失败") } return &model.GamelifePackUrlOut{ Url: url, }, nil } func (s *sUser) Quan8Autologin(ctx context.Context, in *model.Quan8AutologinIn) (out *model.Quan8AutologinOut, err error) { b, err := dao.Stores.Ctx(ctx).WherePri(in.StoreId).Exist() if err != nil { return nil, ecode.Fail.Sub("查找门店失败") } if !b { return nil, ecode.Params.Sub("门店 id 错误") } // Fetch default role value, err := dao.Roles.Ctx(ctx).Where(do.Roles{Code: consts.UserRoleCode}).Fields(dao.Roles.Columns().Code, dao.Roles.Columns().Id).One() if err != nil { return nil, ecode.Fail.Sub("查找角色失败") } // Check if user exists by Quan8 UUID exist, err := dao.Users.Ctx(ctx).Where(do.Users{XyUserId: in.UUID}).Exist() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } var userId int64 if !exist { // User doesn't exist, create new user var username string for { randomStr := grand.Str("abcdefghijklmnopqrstuvwxyz0123456789", 8) username = "qy_" + randomStr count, err := dao.Users.Ctx(ctx).Where(do.Users{Username: username}).Count() if err != nil { return nil, ecode.Fail.Sub("检查用户名失败") } if count == 0 { break // username is unique } } password, err := encrypt.EncryptPassword(consts.DefaultPassword) if err != nil { return nil, ecode.Fail.Sub("加密密码失败") } user := &entity.Users{ XyUserId: in.UUID, Username: username, Nickname: username, PasswordHash: password, Avatar: consts.DefaultUserAvatar, FirstVisitAt: gtime.Now(), LastLoginAt: gtime.Now(), WxPopenId: utility.GenerateUserID("WX"), QqPopenId: utility.GenerateUserID("QQ"), RoleId: value[dao.Roles.Columns().Id].Int64(), LastLoginStoreId: in.StoreId, } userId, err = dao.Users.Ctx(ctx).InsertAndGetId(user) if err != nil { return nil, ecode.Fail.Sub("创建用户失败") } } else { value, err := dao.Users.Ctx(ctx).Where(do.Users{XyUserId: in.UUID}).Fields(dao.Users.Columns().Id).Value() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } userId = value.Int64() if _, err := dao.Users.Ctx(ctx).Where(do.Users{Id: userId}).Update(do.Users{LastLoginAt: gtime.Now(), LastLoginStoreId: in.StoreId}); err != nil { return nil, ecode.Fail.Sub("更新登录时间失败") } } // Generate token token, err := jwt.GenerateToken(&jwt.TokenIn{ UserId: userId, Role: value[dao.Roles.Columns().Code].String(), }) if err != nil { return nil, ecode.Fail.Sub("生成token失败") } return &model.Quan8AutologinOut{ Token: token, }, nil } func (s *sUser) GenerateSceneId(ctx context.Context, in *model.GenerateSceneIdIn) (out *model.GenerateSceneIdOut, err error) { var sceneId string // Check if user exists by UUID if provided if in.UUId != "" { exist, err := dao.Users.Ctx(ctx).Where(do.Users{XyUserId: in.UUId}).Exist() if err != nil { return nil, ecode.Fail.Sub("查找用户失败") } if !exist { return nil, ecode.Params.Sub("用户不存在") } // UUID is provided and user exists, use "is" flag sceneId = fmt.Sprintf("%d_1_%s", in.StoreId, in.UUId) } else { // UUID is empty, use "not" flag with random string randomStr := grand.Str("abcdefghijklmnopqrstuvwxyz0123456789", 8) sceneId = fmt.Sprintf("%d_0_%s", in.StoreId, randomStr) } return &model.GenerateSceneIdOut{ SceneId: sceneId, }, nil }