Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

1513 lines
41 KiB
Go

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
}