package usermod import ( "encoding/json" "fmt" "strconv" "time" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/constant" "91porn-server/common/constant/redisconst" "91porn-server/common/db" "91porn-server/common/log" "91porn-server/common/pageopt" "91porn-server/common/redis" "91porn-server/common/timeutil/timerange" "91porn-server/models" "91porn-server/models/commod" "91porn-server/skd/skdg" "91porn-server/web/webg" "github.com/pkg/errors" "github.com/shopspring/decimal" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // SystemDevIDPrex 马甲账号devID前缀 const SystemDevIDPrex = "system-5rG1eq35Y0F102Qt3BwN2y" const table = models.UserTable func coll(t *db.MongoTool) *db.MongoTool { if t == nil { return mdb.Coll(table) } return t.Coll(table) } // initIndex 索引设置 func initIndex() { many := []mongo.IndexModel{ //batch set indexes //value is the type 1 or -1 { Keys: bson.D{{Key: "uid", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "devID", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "channel", Value: 1}}, }, { Keys: bson.D{{Key: "token", Value: 1}}, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"token": bson.M{"$gt": ""}}), }, { Keys: bson.D{{Key: "promotionCode", Value: 1}}, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"promotionCode": bson.M{"$gt": ""}}), }, { Keys: bson.D{{Key: "vipExpireDate", Value: 1}}, Options: options.Index().SetPartialFilterExpression(bson.M{"vipExpireDate": bson.M{"$gt": time.Time{}}}), }, { Keys: bson.D{{Key: "mobile", Value: 1}}, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"mobile": bson.M{"$gt": ""}}), }, { Keys: bson.D{{Key: "mobileBindAt", Value: -1}}, Options: options.Index().SetSparse(true), }, { Keys: bson.D{{Key: "mobileUnBindAt", Value: -1}}, Options: options.Index().SetSparse(true), }, { Keys: bson.D{{Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "updatedAt", Value: -1}}, }, { Keys: bson.D{{Key: "discBindAt", Value: -1}}, }, { Keys: bson.D{{Key: "name", Value: -1}}, }, { Keys: bson.D{{Key: "hasBanned", Value: 1}, {Key: "createdAt", Value: -1}}, Options: options.Index().SetSparse(true), }, { Keys: bson.D{{Key: "hasLocked", Value: 1}, {Key: "createdAt", Value: -1}}, Options: options.Index().SetSparse(true), }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "discBindAt", Value: -1}}, }, { Keys: bson.D{{Key: "trueScore", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "isDirect", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "isDirect", Value: 1}, {Key: "discBindAt", Value: -1}}, }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "promotionSeqe", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "promotionSeqe", Value: 1}, {Key: "discBindAt", Value: -1}}, }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "isDirect", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "districtCode", Value: 1}, {Key: "isDirect", Value: 1}, {Key: "discBindAt", Value: -1}}, }, { Keys: bson.D{{Key: "autoFollow", Value: 1}, {Key: "createdAt", Value: -1}}, }, { Keys: bson.D{{Key: "registerIP", Value: 1}}, }, { Keys: bson.D{{Key: "officialCert", Value: 1}, {Key: "originalSort", Value: -1}}, }, { Keys: bson.D{{Key: "account", Value: 1}}, }, { Keys: bson.D{{Key: "imUserId", Value: 1}}, Options: options.Index().SetSparse(true), }, { // 用于筛选 Keys: bson.D{{Key: "userType", Value: 1}, {Key: "createdAt", Value: 1}}, }, } if _, err := coll(nil).CreateIndex(many); err != nil { panic(fmt.Sprintf("user model set index err ==>[%+v]", err)) } } func findUser(cond bson.M) (*User, error) { var u User if err := coll(nil).FindOne(&u, cond); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "findUser", table, "FindOne", err), log.Any("cond", cond), ) return nil, err } if u.UID == 0 { return nil, nil } return &u, nil } func findUsers(cond bson.M, opts ...*options.FindOptions) ([]*User, error) { var u []*User if err := coll(nil).Find(&u, cond, opts...); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "findUsers", table, "Find", err), log.Any("cond", cond), ) return u, err } return u, nil } func findUsersMap(cond bson.M) (map[uint64]*User, error) { us, err := findUsers(cond) if err != nil { return nil, err } m := make(map[uint64]*User) for _, u := range us { if u == nil { continue } m[u.UID] = u } return m, nil } func UserMap(uidList []uint64) (map[uint64]*User, error) { if len(uidList) == 0 { return make(map[uint64]*User), nil } filter := bson.M{"uid": bson.M{"$in": uidList}} return findUsersMap(filter) } func userCount(cond bson.M) (int64, error) { return coll(nil).Count(cond) } func FindUIDByCreateTime(start time.Time, end time.Time) ([]uint64, error) { filter := bson.M{ "createdAt": bson.M{ "$gte": start, "$lt": end, }, } users, err := findUsers(filter) if err != nil { return nil, err } uidList := make([]uint64, len(users)) for i, user := range users { uidList[i] = user.UID } return uidList, nil } // FindUsersByUID uids查找用户 func FindUsersByUID(uids []uint64) ([]*User, error) { return findUsers(bson.M{"uid": bson.M{"$in": uids}}) } // FindUsersByUID uids查找用户 func FindUsersMapByUID(uids []uint64) (map[uint64]*User, error) { users, err := findUsers(bson.M{"uid": bson.M{"$in": uids}}) if err != nil { return nil, err } var uMap = make(map[uint64]*User) for _, v := range users { _v := v uMap[v.UID] = _v } return uMap, nil } // FindUsersByKeyword 通过关键字获取用户列表 func FindUsersByKeyword(keyword string, skip int64, limit int64) ([]*User, error) { opt := (&options.FindOptions{}).SetSkip(skip).SetLimit(limit) filter := bson.M{ "name": bson.M{ "$regex": fmt.Sprintf("^%s", keyword), }, } return findUsers(filter, opt) } // FindUsersByCreateTime 新用户列表 func NewUserList(skip int64, limit int64) ([]*User, error) { sort := bson.D{{Key: "updatedAt", Value: -1}} opt := (&options.FindOptions{}).SetSort(sort).SetSkip(skip).SetLimit(limit) return findUsers(bson.M{}, opt) } // InsertUser 插入用户 func InsertUser(u *User) error { now := time.Now() u.CreatedAt = now u.UpdatedAt = now if u.Mobile != "" { u.MobileBindAt = &now } if _, err := coll(nil).InsertOne(u); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "InsertUser", table, "InsertOne", err), log.Any("u", u), ) return err } return nil } // FindUserByDevID 设备id查找用户 func FindUserByDevID(devID string) (*User, error) { return findUser(bson.M{"devID": devID}) } // FindUserByToken token查找用户 func FindUserByToken(token string) (*User, error) { return findUser(bson.M{"token": token}) } func FindUserByUIDForNoCache(uid uint64) (*User, error) { return findUser(bson.M{"uid": uid}) } func RefreshCache(uid uint64) { redisCachDel(uid) } func RefreshCacheAndGetUser(uid uint64) (*User, error) { redisCachDel(uid) return FindUserByUID(uid) } func FindUserByUIDTrans(t *db.MongoTool, uid uint64) (*User, error) { var u User if err := coll(t).FindOne(&u, bson.M{"uid": uid}); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "findUser", table, "FindUserByUIDTrans", err)) return nil, err } if u.UID == 0 { return nil, nil } return &u, nil } // FindUserByUID 根据uid获取用户信息。首先尝试缓存,若从缓存中获取失败,再去DB获取用户信息。 func FindUserByUID(uid uint64) (*User, error) { redisKey := redisconst.DataCachKey(table, strconv.FormatUint(uid, 10)) redisc := getRedis() str, err := redisc.Get(redisKey) if err != nil { // redis 错误不向上报告 log.Error("FindUserByUID redisc.Get", log.Any("uid", uid), log.Any("redisKey", redisKey), log.E(err)) } user := &User{} if str != nil { if err = json.Unmarshal([]byte(*str), user); err == nil { return user, nil } // json.Unmarshal的错误不向上报告,而是尝试去DB获取用户 log.Error("FindUserByUID json.Unmarshal", log.Any("uid", uid), log.Any("redisKey", redisKey), log.E(err)) } user, err = findUser(bson.M{"uid": uid}) if err != nil { return nil, err } if user == nil { return nil, errors.New("empty user") } common.Go(func() { jsonBytes, err := json.Marshal(user) if err != nil { log.Error("FindUserByUID json.Marshal", log.Any("uid", uid), log.E(err)) return } if err := redisc.Set(redisKey, string(jsonBytes), redisconst.DataCachExpire); err != nil { log.Error("FindUserByUID redisc.Set", log.Any("uid", uid), log.Any("redisKey", redisKey), log.E(err)) } }) return user, err } // FindUserPromotionCode uid查找用户 func FindUserPromotionCode(promotionCode string) (*User, error) { return findUser(bson.M{"promotionCode": promotionCode}) } // FindUserByMobile手机号查找用户 func FindUserByMobile(mobile string) (*User, error) { return findUser(bson.M{"mobile": mobile}) } // FindUserByAccount 根据账号查找用户 func FindUserByAccount(account string) (*User, error) { return findUser(bson.M{"account": account}) } func findOneAndUpdateUser(t *db.MongoTool, filter bson.M, up bson.M) (*User, error) { up["updatedAt"] = time.Now() u := &User{} if err := coll(t).FindOneAndUpdate(u, filter, bson.M{"$set": up}); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "findOneAndUpdateUser", table, "FindOneAndUpdate", err)) return u, err } return u, nil } func updateUsers(t *db.MongoTool, filter bson.M, up bson.M) (*mongo.UpdateResult, error) { up["updatedAt"] = time.Now() return coll(t).UpdateMany(filter, bson.M{"$set": up}) } // UpdateTrans 修改用户信息(开启事务) func UpdateTrans(t *db.MongoTool, uid uint64, set UserSelector) (*User, error) { defer redisCachDel(uid) setM, err := common.ToBsonM(&set) if err != nil { log.ZapLog.Warn("user UpdateTrans ToBsonM fail", log.E(err)) return nil, err } return findOneAndUpdateUser(t, bson.M{"uid": uid}, setM) } // UpdateTrans 修改用户信息(开启事务) func UpdateVIP(t *db.MongoTool, uid uint64, vipExpireDate time.Time, set UserSelector) error { defer redisCachDel(uid) now := time.Now() set.UpdatedAt = &now res, err := coll(t).UpdateOne(bson.M{"uid": uid, "vipExpireDate": vipExpireDate}, bson.M{"$set": set}) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateVIP", table, "UpdateOne", err), log.Any("uid", uid), log.Any("vipExpireDate", vipExpireDate.String()), log.Any("set", set), ) return err } if res.ModifiedCount == 0 { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateVIP", table, "res.ModifiedCount == 0", err), log.Any("uid", uid), log.Any("set", set), ) return errors.New("user UpdateVIP ModifiedCount err") } return nil } func UpdateManyTrans(t *db.MongoTool, uid []uint64, set UserSelector) (*mongo.UpdateResult, error) { defer redisCachDelMany(uid) setM, err := common.ToBsonM(&set) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateManyTrans", table, "ToBsonM", err), log.Any("uid", uid), log.Any("set", set), ) return nil, err } return updateUsers(t, bson.M{"uid": bson.M{"$in": uid}}, setM) } func UpdateMany(uids []uint64, set UserSelector) (*mongo.UpdateResult, error) { return UpdateManyTrans(nil, uids, set) } // Update ()修改用户信息 func Update(uid uint64, set UserSelector) (*User, error) { t := time.Now() set.UpdatedAt = &t return UpdateTrans(nil, uid, set) } // UpdateSnapVip ()修改用户信息 func UpdateSnapVip(uid uint64, set UserSelector) (*User, error) { t := time.Now() set.UpdatedAt = &t s, _ := common.ToBsonM(set) return findOneAndUpdateUser(nil, bson.M{"uid": uid, "snapVip": 0}, s) } // UpdateUserCountWorks 更新用户作品数 func UpdateUserCountWorks(m map[uint64]int64) error { var writes []mongo.WriteModel for uid, n := range m { filter := bson.M{ "uid": uid, } update := bson.M{ "$set": bson.M{ "totalWorks": n, }, } // 这里不去更新 updatedAt(更新时间),以免引起连锁更新,导致服务器高负载 writes = append(writes, mongo.NewUpdateOneModel(). SetFilter(filter). SetUpdate(update), ) if len(writes) > 100 { opt := options.BulkWrite().SetOrdered(false) _, err := coll(nil).Bulk(writes, opt) if err != nil { return err } writes = writes[:0] } } if len(writes) == 0 { return nil } opt := options.BulkWrite().SetOrdered(false) _, err := coll(nil).Bulk(writes, opt) return err } func ChangeVisit(uid uint64, lastVer, lastSysType string, lastVisitAt time.Time) error { update := bson.M{ "$set": bson.M{"lastVisitAt": lastVisitAt, "lastVer": lastVer, "lastSysType": lastSysType}, "$inc": bson.M{"loginDays": 1}, } if _, err := coll(nil).UpdateOne(bson.M{"uid": uid}, update); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ChangeVisit", table, "UpdateOne", err)) return err } return nil } // ClaimDefaultEntryVersion 将当前 App 版本标记为已处理默认入口。 // 同一用户同一版本只有第一次调用会返回 true,用于避免重复触发默认入口。 func ClaimDefaultEntryVersion(uid uint64, ver string) (bool, error) { if uid == 0 || ver == "" { return false, nil } cacheKey := redisconst.DataCachKey("default_entry_version", strconv.FormatUint(uid, 10)) redisc := getRedis() if redisc != nil { cachedVer, err := redisc.Get(cacheKey) if err != nil { log.Warn("ClaimDefaultEntryVersion cache get failed", log.Any("uid", uid), log.E(err)) } else if cachedVer != nil && *cachedVer == ver { return false, nil } } res, err := coll(nil).UpdateOne( bson.M{"uid": uid, "defaultEntryHandledVer": bson.M{"$ne": ver}}, bson.M{"$set": bson.M{"defaultEntryHandledVer": ver}}, ) if err != nil { log.Warn("ClaimDefaultEntryVersion failed", log.Any("uid", uid), log.E(err)) return false, err } if res.ModifiedCount == 0 { if redisc != nil { _ = redisc.Set(cacheKey, ver, 30*24*time.Hour) } return false, nil } redisCachDel(uid) if redisc != nil { if err := redisc.Set(cacheKey, ver, 30*24*time.Hour); err != nil { log.Warn("ClaimDefaultEntryVersion cache set failed", log.Any("uid", uid), log.E(err)) } } return true, nil } func BulkWrite(models []mongo.WriteModel) error { _, err := coll(nil).Bulk(models, options.BulkWrite().SetOrdered(false)) return err } func ChangeEmail(uid uint64, email string) (bool, error) { defer redisCachDel(uid) filter := bson.M{ "uid": uid, "emailCheckedAt": bson.M{"$exists": true}, } update := bson.M{ "$set": bson.M{ "email": email, "updatedAt": time.Now(), }, } res, err := coll(nil).UpdateOne(filter, update) if err != nil { return false, errors.Wrap(err, fmt.Sprintf("change user[%d] email[%s] falied", uid, email)) } return res.ModifiedCount > 0, nil } func ChangeMobile(uid uint64, mobile string) (bool, error) { defer redisCachDel(uid) //mobileBindAt存在,更换手机号 filter := bson.M{ "uid": uid, "mobileBindAt": bson.M{"$exists": true}, } update := bson.M{ "$set": bson.M{ "mobile": mobile, "updatedAt": time.Now(), }, } ret, err := coll(nil).UpdateOne(filter, update) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ChangeMobile", table, "UpdateOne", err), log.Any("uid", uid), log.Any("mobile", mobile), ) switch exception := err.(type) { case mongo.WriteException: for _, e := range exception.WriteErrors { if e.Code == 11000 { //https://docs.mongodb.com/manual/reference/method/db.collection.insert/index.html return false, MobileHasBindError{uid, mobile} } } } return false, err } return ret.MatchedCount+ret.ModifiedCount > 0, nil } func BindAccount(uid, newUid uint64, account, password, devID string) (bool, error) { defer redisCachDel(uid) filter := bson.M{ "uid": uid, "$or": []bson.M{ {"accountBindAt": bson.M{"$exists": false}}, {"accountBindAt": nil}, }, } now := time.Now() set := bson.M{ "account": account, "passWord": password, "uid": newUid, "userType": UserUnkown, "devID": devID, "accountBindAt": now, "updatedAt": now, "createdAt": now, } res, err := coll(nil).UpdateOne(filter, bson.M{"$set": set}) if err != nil { return false, errors.Wrap(err, fmt.Sprintf("bind user[%d] account[%s] failed", uid, account)) } return res.ModifiedCount > 0, nil } func BindEmail(uid uint64, email, password string) (bool, error) { defer redisCachDel(uid) filter := bson.M{ "uid": uid, "$or": []bson.M{ {"emailCheckedAt": bson.M{"$exists": false}}, {"emailCheckedAt": nil}, }, } now := time.Now() set := bson.M{ "email": email, "emailCheckedAt": now, "updatedAt": now, } if password != "" { set["passWord"] = password } res, err := coll(nil).UpdateOne(filter, bson.M{"$set": set}) if err != nil { return false, errors.Wrap(err, fmt.Sprintf("bind user[%d] email[%s] failed", uid, email)) } return res.ModifiedCount > 0, nil } func BindMobile(t *db.MongoTool, uid uint64, mobile, passWord string) (bool, error) { defer redisCachDel(uid) now := time.Now() //mobileBindAt不存在,绑定手机号 filter := bson.M{ "uid": uid, "$or": bson.A{ bson.M{"mobileBindAt": bson.M{"$exists": false}}, bson.M{"mobileBindAt": nil}, }, } set := bson.M{ "mobile": mobile, "mobileBindAt": now, "updatedAt": now, } if passWord != "" { set["passWord"] = passWord } update := bson.M{ "$set": set, } if _, err := coll(t).UpdateOne(filter, update); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "BindMobile", table, "UpdateOne", err), log.Any("uid", uid), log.Any("mobile", mobile), ) return false, err } return true, nil } func UnBindMobile(uid uint64) (bool, error) { defer redisCachDel(uid) //mobileBindAt存在,解绑手机号 now := time.Now() filter := bson.M{ "uid": uid, "mobileBindAt": bson.M{"$exists": true}, } update := bson.M{ "$set": bson.M{ "mobile": "", "mobileUnBindAt": now, "updatedAt": now, }, } ret, err := coll(nil).UpdateOne(filter, update) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UnBindMobile", table, "UpdateOne", err), log.Any("uid", uid), ) return false, err } return ret.MatchedCount+ret.ModifiedCount > 0, nil } func UpdateUserToken(uid uint64, token string) (*User, error) { return Update(uid, UserSelector{Token: &token}) } func UpdateUserVipLevel(uid uint64, lvl int) (*User, error) { return Update(uid, UserSelector{VipLevel: &lvl}) } func UpdateSelf(uid uint64, set UserModifyReq) (*User, error) { defer redisCachDel(uid) return Update(uid, UserSelector{ Gender: set.Gender, Name: set.Name, Portrait: set.Portrait, Background: set.Background, Summary: set.Summary, Region: set.Region, Birthday: set.Birthday, AppLock: set.AppLock, UpdatedAt: set.UpdatedAt, }) } // 扣减次数 func DincUserWatchCount(uid uint64) (*User, error) { defer redisCachDel(uid) filter := bson.M{"uid": uid, "watchCount": bson.M{"$gte": 1}} u := &User{} if err := coll(nil).FindOneAndUpdate(u, filter, bson.M{"$inc": bson.M{"watchCount": -1}}); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "DincUserWatchCount", table, "FindOneAndUpdate", err), log.Any("uid", uid), ) return u, err } return u, nil } func UpdateUserWatchCount(uid uint64, count int64) (*User, error) { defer redisCachDel(uid) filter := bson.M{"uid": uid, "watchCount": bson.M{"$gte": 0}} u := &User{} if err := coll(nil).FindOneAndUpdate(u, filter, bson.M{"$set": bson.M{"watchCount": count}}); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateUserWatchCount", table, "FindOneAndUpdate", err), log.Any("uid", uid), ) return u, err } return u, nil } // 后台devID重置 func ResetDevID(uid uint64, devID string) error { defer redisCachDel(uid) if _, err := coll(nil).UpdateOne(bson.M{"uid": uid}, bson.M{"$set": bson.M{"devID": devID}}); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ResetDevID", table, "UpdateOne", err), log.Any("uid", uid), log.Any("devID", devID), ) return err } return nil } func GetUsersBaseInfo(uids []uint64) ([]*BaseInfo, error) { var data []*BaseInfo if len(uids) == 0 { return data, nil } us, err := findUsers(bson.M{"uid": bson.M{"$in": uids}}) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetUsersBaseInfo", table, "findUsers", err), log.Any("uids", uids), ) return data, err } data = make([]*BaseInfo, len(us)) for i, u := range us { data[i] = &BaseInfo{ UID: u.UID, Name: u.Name, Gender: u.Gender, Portrait: u.Portrait, HasBanned: u.HasBanned, HasLocked: u.HasLocked, } } return data, nil } func GetUsersBaseInfoWithVip(uids []uint64) ([]*BaseInfoVip, error) { var data []*BaseInfoVip if len(uids) == 0 { return data, nil } us, err := findUsers(bson.M{"uid": bson.M{"$in": uids}}) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetUsersBaseInfoWithVip", table, "findUsers", err), log.Any("uids", uids), ) return data, err } data = make([]*BaseInfoVip, len(us)) for i, u := range us { isVip := false if u.VipExpireDate.After(time.Now()) { isVip = true } data[i] = &BaseInfoVip{ UID: u.UID, Name: u.Name, Gender: u.Gender, Portrait: u.Portrait, HasLocked: u.HasLocked, HasBanned: u.HasBanned, VipLevel: u.VipLevel, IsVip: isVip, //RechargeLevel: , SuperUser: u.SuperUser, ActiveValue: u.ActiveValue, OfficialCert: u.OfficialCert, Age: u.Age(), Follows: u.Follows, Fans: u.Fans, TotalWorks: u.TotalWorks, Summary: &u.Summary, Awards: u.Awards, UpTag: u.UpTag, VipName: u.VipName, VipExpireDate: u.VipExpireDate, } } return data, nil } func GetUsersBaseInfoVIPMap(uids []uint64) (map[uint64]*BaseInfoVip, error) { m := make(map[uint64]*BaseInfoVip) infos, err := GetUsersBaseInfoWithVip(uids) if err != nil { return m, err } for _, info := range infos { m[info.UID] = info } return m, nil } func GetUsersBaseInfoMap(uids []uint64) (map[uint64]*BaseInfo, error) { m := make(map[uint64]*BaseInfo) infos, err := GetUsersBaseInfo(uids) if err != nil { return m, err } for _, info := range infos { m[info.UID] = info } return m, nil } // CountByCreatedAt 时间内注册数 func CountByCreatedAt(start, end time.Time, mats ...Matcher) (int64, error) { mats = append(mats, (&CreatedAtGTEAndLTMatch{GTE: &start, LT: &end}).New()) return Count(mats...) } // NewAndBindInfoCountByTime func BindCountByTime(start time.Time, end time.Time) (int64, error) { filter := bson.M{ "mobileBindAt": bson.M{ "$gte": start, "$lt": end, }, } return userCount(filter) } func UnBindCountByTime(start time.Time, end time.Time) (int64, error) { filter := bson.M{ "mobileUnBindAt": bson.M{ "$gte": start, "$lt": end, }, } return userCount(filter) } // NewAndBindCountByTime func NewAndBindCountByTime(createStart, createEnd, start, end time.Time) (int64, error) { filter := bson.M{ "mobile": bson.M{ "$ne": "", }, "createdAt": bson.M{ "$gte": createStart, "$lt": createEnd, }, "mobileBindAt": bson.M{ "$gte": start, "$lt": end, }, } return userCount(filter) } // ChannelNewUserCountByTime 时间内渠道注册数 func ChannelNewUserCountByTime(start time.Time, end time.Time) (map[string]int64, error) { pipeLine := []bson.M{ {"$match": bson.M{"createdAt": bson.M{"$gte": start, "$lt": end}}}, {"$group": bson.M{"_id": "$channel", "count": bson.M{"$sum": 1}}}, } docList := []struct { Channel string `bson:"_id"` Count int64 `bson:"count"` }{} if err := coll(nil).Aggregate(&docList, pipeLine); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ChannelNewUserCountByTime", table, "Aggregate", err), log.Any("start", start), log.Any("end", end), ) return nil, err } ret := make(map[string]int64, len(docList)) for _, doc := range docList { ret[doc.Channel] = doc.Count } return ret, nil } // ChannelBindUserCountByTime 渠道绑定用户数 func ChannelBindUserCountByTime(start time.Time, end time.Time) (map[string]int64, error) { pipeLine := []bson.M{ { "$match": bson.M{ "mobile": bson.M{ "$ne": "", }, "mobileBindAt": bson.M{ "$gte": start, "$lt": end, }, }, }, { "$group": bson.M{ "_id": "$channel", "count": bson.M{"$sum": 1}, }, }, } docList := []struct { Channel string `bson:"_id"` Count int64 `bson:"count"` }{} if err := coll(nil).Aggregate(&docList, pipeLine); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ChannelBindUserCountByTime", table, "Aggregate", err), log.Any("start", start), log.Any("end", end), ) return nil, err } ret := make(map[string]int64, len(docList)) for _, doc := range docList { ret[doc.Channel] = doc.Count } return ret, nil } // ChannelNewAndBindUserCountByTime 渠道新增并绑定用户数 func ChannelNewAndBindUserCountByTime(start time.Time, end time.Time) (map[string]int64, error) { dayRange := timerange.LocDayRange(start) pipeLine := []bson.M{ { "$match": bson.M{ "mobile": bson.M{ "$ne": "", }, "createdAt": bson.M{ "$gte": dayRange.Head, "$lt": dayRange.Tail, }, "mobileBindAt": bson.M{ "$gte": start, "$lt": end, }, }, }, { "$group": bson.M{ "_id": "$channel", "count": bson.M{"$sum": 1}, }, }, } docList := []struct { Channel string `bson:"_id"` Count int64 `bson:"count"` }{} if err := coll(nil).Aggregate(&docList, pipeLine); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "ChannelNewAndBindUserCountByTime", table, "Aggregate", err), log.Any("start", start), log.Any("end", end), ) return nil, err } ret := make(map[string]int64, len(docList)) for _, doc := range docList { ret[doc.Channel] = doc.Count } return ret, nil } func UIDList(mats ...Matcher) ([]uint64, error) { filter := pageopt.MergeM(mats) list := []struct { UID uint64 `bson:"uid"` }{} opt := (&options.FindOptions{}).SetProjection(bson.M{ "uid": 1, }) if err := coll(nil).Find(&list, filter, opt); err != nil { return nil, err } uidList := make([]uint64, len(list)) for i, v := range list { uidList[i] = v.UID } return uidList, nil } func UIDListByCreateTime(start, end time.Time, mats ...Matcher) ([]uint64, error) { mats = append(mats, (&CreatedAtGTEAndLTMatch{GTE: &start, LT: &end}).New()) return UIDList(mats...) } func UIDListByCreateTimeAndUIDS(start, end time.Time, uids []uint64, mats ...Matcher) ([]uint64, error) { mats = append(mats, (&UIDInMatch{UIDS: uids}).New()) return UIDListByCreateTime(start, end, mats...) } func UserListByCreateTimeAndUIDS(start, end time.Time, uids []uint64, mats ...Matcher) ([]User, error) { mats = append(mats, (&CreatedAtGTEAndLTMatch{GTE: &start, LT: &end}).New(), (&UIDInMatch{UIDS: uids}).New()) filter := pageopt.MergeM(mats) list := make([]User, 0, len(uids)) return list, coll(nil).Find(&list, filter) } func ChannelMapByCreateTime(start, end time.Time) (map[uint64]string, error) { filter := bson.M{ "createdAt": bson.M{ "$gte": start, "$lt": end, }, } list := make([]struct { Channel string `bson:"channel"` UID uint64 `bson:"uid"` }, 0) if err := coll(nil).Find(&list, filter); err != nil { return nil, err } m := make(map[uint64]string, len(list)) for _, v := range list { m[v.UID] = v.Channel } return m, nil } // 获取ES同步数据 func GetUserListByUpdateTimeRange(start time.Time, end time.Time, page int, size int) (data []User, hasNext bool, err error) { var query = bson.M{ "updatedAt": bson.M{"$gte": start, "$lt": end}, } opts := options.FindOptions{} opts.SetSort(bson.D{{Key: "_id", Value: 1}}) opts.SetSkip(int64((page - 1) * size)).SetLimit(int64(size) + 1) if err = coll(nil).Find(&data, query, &opts); err != nil { log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetUserListByUpdateTimeRange", table, "Find", err), log.Any("start", start), log.Any("end", end), ) return } if len(data) > size { hasNext = true data = data[:size] } return } func redisCachDel(uid uint64) { redisKey := redisconst.DataCachKey(table, strconv.FormatUint(uid, 10)) if _, err := getRedis().Del(redisKey); err != nil { log.Error("redis del user, ", log.Any("err:", err.Error()), log.Any("uid:", uid)) } } func redisCachDelMany(uids []uint64) { keys := make([]string, len(uids)) for i, v := range uids { keys[i] = redisconst.DataCachKey(table, strconv.FormatUint(v, 10)) } _, _ = getRedis().Del(keys...) } func getRedis() *redis.Client { if appg.Redis != nil { return appg.Redis } if webg.Redis != nil { return webg.Redis } if skdg.Redis != nil { return skdg.Redis } return nil } func GetLockedUIDMap() (map[uint64]byte, error) { filter := bson.M{ "hasLocked": true, } list := make([]struct { UID uint64 `bson:"uid"` }, 0) opt := (&options.FindOptions{}).SetProjection(bson.M{ "uid": 1, }) if err := coll(nil).Find(&list, filter, opt); err != nil { return nil, err } uidMap := make(map[uint64]byte, len(list)) for _, v := range list { uidMap[v.UID] = 1 } return uidMap, nil } func DistrictCodeMap(uidList []uint64) (map[uint64]string, error) { if len(uidList) == 0 { return make(map[uint64]string), nil } filter := bson.M{"uid": bson.M{"$in": uidList}} userList, err := findUsers(filter) if err != nil { return nil, err } m := make(map[uint64]string, len(userList)) for _, v := range userList { m[v.UID] = v.DistrictCode } return m, nil } func GetSystemUsers() (data []User, err error) { if err = coll(nil).Find(&data, bson.M{"uid": bson.M{"$lte": constant.RobotUIDLimit}}); err != nil { log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetSystemUsers", table, "Find", err)) return } return } // SetDeductType 设置扣量类型 func SetDeductType(t *db.MongoTool, uid uint64, deductType commod.DeductType) error { filter := bson.M{"uid": uid} update := bson.M{"$unset": bson.M{"deductType": deductType}} if deductType != commod.NotDed { update = bson.M{"$set": bson.M{"deductType": deductType}} } _, err := coll(t).UpdateOne(filter, update) return err } func GetCountMapByHour(discCode string, start, end time.Time, mats ...Matcher) ([]int, map[int]int64, int64, error) { mats = append(mats, (&DistrictCodeMatch{&discCode}).New(), (&CreatedAtGTEAndLTMatch{GTE: &start, LT: &end}).New(), ) filter := pageopt.MergeM(mats) opt := (&options.FindOptions{}).SetProjection(bson.M{ "createdAt": 1, }) var list []struct { CreatedAt time.Time `bson:"createdAt"` //创建时间 } if err := coll(nil).Find(&list, filter, opt); err != nil { log.Error("usermod GetBuyVipCountMap error", log.E(err)) return nil, nil, 0, err } m := make(map[int]int64) startH := start.Hour() lastH := end.Hour() + (end.Day()-start.Day())*24 hs := make([]int, lastH-startH+1) for i, j := startH, 0; i <= lastH; i++ { m[i] = 0 hs[j] = i j++ } var total int64 for _, v := range list { hour := v.CreatedAt.Hour() + (v.CreatedAt.Day()-start.Day())*24 m[hour] += 1 total++ } return hs, m, total, nil } // IncFollow 增加关注数 func IncFollow(uid uint64) error { cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"follows": 1}} _, err := coll(nil).UpdateOne(cond, update) return err } // IncFollowByNum 增加关注数 func IncFollowByNum(uid uint64, num int64) error { cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"follows": num}} _, err := coll(nil).UpdateOne(cond, update) return err } // DecFollow 减去关注数 func DecFollow(uid uint64) error { cond := bson.M{"uid": uid, "follows": bson.M{"$gt": 0}} update := bson.M{"$inc": bson.M{"follows": -1}} _, err := coll(nil).UpdateOne(cond, update) return err } // IncFans 增加粉丝数 func IncFans(uid uint64) error { cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"fans": 1}} _, err := coll(nil).UpdateOne(cond, update) return err } // IncLikeCount 增加点赞数 func IncLikeCount(uid uint64) error { cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"likeCount": 1}} _, err := coll(nil).UpdateOne(cond, update) return err } // DecFans 减去粉丝数 func DecFans(uid uint64) error { cond := bson.M{"uid": uid, "fans": bson.M{"$gt": 0}} update := bson.M{"$inc": bson.M{"fans": -1}} _, err := coll(nil).UpdateOne(cond, update) return err } // InitFansFollows 初始化关注数和粉丝数 func InitFansFollows(uid uint64, followCnt int64, fansCnt int64) error { cond := bson.M{"uid": uid} update := bson.M{"$set": bson.M{"follows": followCnt, "fans": fansCnt, "followsSetFlag": true}} _, err := coll(nil).UpdateOne(cond, update) return err } // 添加ai脱衣次数 func IncUserAiUndress(t *db.MongoTool, uid uint64, count uint64) error { _, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{"$inc": bson.M{"aiUndressCount": count}}) return err } // UpdateById 修改用户信息 func UpdateById(t *db.MongoTool, uid uint64, bannedTime time.Time) error { defer redisCachDel(uid) _, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{"$set": bson.M{"bannedTime": bannedTime}}) return err } func DecUserAiUndress(t *db.MongoTool, uid uint64, count uint64) error { _, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{"$inc": bson.M{"aiUndressCount": -int64(count)}}) return err } // IncRewarded 增加打赏金额 func IncRewarded(uid uint64, decimal decimal.Decimal) error { cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"rewarded": decimal}} _, err := coll(nil).UpdateOne(cond, update) return err } func StatcenterSyncList(uid uint64, size int64) (UserSlice, error) { opt := (&options.FindOptions{}) opt.SetLimit(size) opt.SetSort(bson.M{"uid": 1}) filter := bson.M{"uid": bson.M{"$gt": uid}, "userType": bson.M{"$in": []UserType{UserUnkown, UserLoufengAgent}}} userList := UserSlice{} return userList, coll(nil).Find(&userList, filter, opt) } func GetRandomSystemUsers(size uint64) (data []*User, err error) { data = make([]*User, 0) var pipeline = []bson.M{ {"$match": bson.M{"uid": bson.M{"$lte": constant.RobotUIDLimit}}}, {"$sample": bson.M{"size": size}}, } if err = coll(nil).Aggregate(&data, pipeline); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetRandomSystemUsers", table, "Aggregate", err), log.Any("pipeline", pipeline)) return } return } // GetOriginalBlogger 获取原创博主 func GetOriginalBlogger(skip, limit int64) (data []*User, hasNext bool, err error) { opts := options.Find().SetSort(bson.D{{Key: "originalSort", Value: -1}}).SetSkip(skip).SetLimit(limit + 1) cond := bson.M{"officialCert": true} if err = coll(nil).Find(&data, cond, opts); err != nil { return } if len(data) == int(limit)+1 { hasNext = true data = data[:limit] } return } // 同步数据查询绑定手机用户 func StatcenterSyncBindUserList(time time.Time, size int64) (UserSlice, error) { opt := (&options.FindOptions{}) opt.SetLimit(size) opt.SetSort(bson.M{"mobileBindAt": 1}) filter := bson.M{"mobileBindAt": bson.M{"$gt": time}} userList := UserSlice{} return userList, coll(nil).Find(&userList, filter, opt) } func OfficialCertList(page, size uint64, subType int) ([]*User, bool, error) { cond := bson.M{} // 原创---工作室 cond["officialCert"] = true opts := options.Find().SetLimit(int64(size + 1)).SetSkip(int64((page - 1) * size)).SetSort(bson.D{{Key: "originalSort", Value: -1}}) var users []*User if err := coll(nil).Find(&users, cond, opts); err != nil { return nil, false, err } var hasNext bool if uint64(len(users)) > size { hasNext = true users = users[:size] } return users, hasNext, nil } // PretendList 获取马甲号列表 func PretendList(skip int64, limit int64) ([]*User, error) { filter := bson.M{ "uid": bson.M{ "$gt": 100001, "$lt": 100200, }, } return findUsers(filter, options.Find().SetLimit(limit).SetSkip(skip)) } // 获取马甲号总数 func PretendCount() (int64, error) { return coll(nil).Count(bson.M{ "uid": bson.M{ "$gt": 100001, "$lt": 100200, }, }) } // IncVideoDeduction 视频播放量更新 func IncVideoDeduction(uid uint64, check bool) error { defer redisCachDel(uid) cond := bson.M{"uid": uid} inc := bson.M{"videoDeductionPayCount": 1} if check { inc["videoDeductionCount"] = 1 } update := bson.M{"$inc": inc} _, err := coll(nil).UpdateOne(cond, update) return err } // IncCoverCount 出售图片量更新 func IncCoverCount(uid uint64) error { _, err := coll(nil).UpdateOne(bson.M{"uid": uid}, bson.M{"$inc": bson.M{"coverPayCount": 1}}) return err } // IncUploadCount 上传次数更新 func IncUploadCount(uid uint64, inc UploadCountInc) error { defer redisCachDel(uid) _, err := coll(nil).UpdateOne(bson.M{"uid": uid}, bson.M{"$inc": inc}) return err } // LouFengUnlockTimesIncr 增加楼凤解锁次数 func LouFengUnlockTimesIncr(t *db.MongoTool, uid uint64, times int) error { result, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{"$inc": bson.M{"louFengUnlockTimes": times}}) if err != nil { return err } if result.ModifiedCount == 0 { return errors.New("result.ModifiedCount is 0") } return nil } // VipInfoChange 会员信息变更 func VipInfoChange(t *db.MongoTool, uid uint64, levle int, expire time.Time) error { result, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{ "$set": bson.M{ "vipExpireDate": expire, "vipLevel": levle, }}) if err != nil { return err } if result.ModifiedCount == 0 { return errors.New("result.ModifiedCount is 0") } return nil } func FindByCreateTime(start time.Time, end time.Time) (data []UserStat, err error) { filter := bson.M{ "createdAt": bson.M{ "$gte": start, "$lt": end, }, } if err = coll(nil).Find(&data, filter); err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "findUsers", table, "Find", err), log.Any("cond", filter), ) } return } // IncDynamic 增加动态数 func IncDynamic(uid uint64) error { defer redisCachDel(uid) cond := bson.M{"uid": uid} update := bson.M{"$inc": bson.M{"dynamics": 1}} _, err := coll(nil).UpdateOne(cond, update) return err } // UpdateUserById 修改用户信息(开启事务) func UpdateUserById(t *db.MongoTool, uid uint64, set UserSelector) error { defer redisCachDel(uid) now := time.Now() set.UpdatedAt = &now res, err := coll(t).UpdateOne(bson.M{"uid": uid}, bson.M{"$set": set}) if err != nil { log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateVIP", table, "UpdateOne", err), log.Any("uid", uid), log.Any("set", set), ) return err } if res.ModifiedCount == 0 { err = errors.New("user UpdateVIP ModifiedCount err") log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateVIP", table, "res.ModifiedCount == 0", err), log.Any("uid", uid), log.Any("set", set), ) return err } return nil } // DeleteGuestBeforeDate 删除游客数据 func DeleteGuestBeforeDate(t *db.MongoTool, tm time.Time) error { _, err := coll(t).DeleteMany(bson.M{"updatedAt": bson.M{"$lt": tm}, "userType": UserTourists}) return err } // FetchList 条件获取列表 func FetchList(filter primitive.M, opts *options.FindOptions, count ...*int64) (out []*User, hasNext bool, err error) { if opts == nil { opts = options.Find() } if opts.Limit == nil { opts.SetLimit(1000) } if opts.Sort == nil { opts.SetSort(bson.D{{Key: "_id", Value: -1}}) } // 不需要统计总条数 就不要创建count,避免无用的查询 if len(count) == 1 && count[0] != nil { *count[0], err = coll(nil).Count(filter) if err != nil { return nil, false, err } } limit := int(*opts.Limit) opts.SetLimit(int64(limit + 1)) err = coll(nil).Find(&out, filter, opts) if err != nil { log.Error(fmt.Sprintf("[METHOD-FetchList]==> Model %s Find fail error:%+v:", table, err), log.Any("filter", filter)) return out, false, err } hasNext = len(out) > limit if hasNext { out = out[:limit] } return out, hasNext, nil }