2045 lines
59 KiB
Go
2045 lines
59 KiB
Go
package userser
|
||
|
||
import (
|
||
"91porn-server/app/appg"
|
||
"91porn-server/app/middleware/authuser"
|
||
"91porn-server/app/service/adser"
|
||
"91porn-server/app/service/imser"
|
||
"91porn-server/app/service/mediaser"
|
||
"91porn-server/app/service/messageser"
|
||
"91porn-server/app/service/notiser"
|
||
"91porn-server/app/service/productser"
|
||
"91porn-server/app/service/proxyser"
|
||
"91porn-server/app/service/sys_config"
|
||
"91porn-server/app/service/taskser"
|
||
"91porn-server/app/service/vidhelpser"
|
||
"91porn-server/app/service/walletser"
|
||
"91porn-server/common"
|
||
"91porn-server/common/constant"
|
||
"91porn-server/common/constant/redisconst"
|
||
"91porn-server/common/crypt"
|
||
"91porn-server/common/datacenter"
|
||
"91porn-server/common/db"
|
||
"91porn-server/common/log"
|
||
sli "91porn-server/common/slice"
|
||
"91porn-server/common/stderr"
|
||
"91porn-server/common/timeutil"
|
||
"91porn-server/common/timeutil/timerange"
|
||
"91porn-server/common/version"
|
||
"91porn-server/common/ysqr"
|
||
"91porn-server/middleware/ua"
|
||
"91porn-server/models/cache/aiplazadata"
|
||
"91porn-server/models/cache/sysconfdata"
|
||
"91porn-server/models/commod"
|
||
"91porn-server/models/l/exchlogmod"
|
||
"91porn-server/models/l/payvidlgmod"
|
||
"91porn-server/models/s/statrecordmod"
|
||
"91porn-server/models/v/aiplazamod"
|
||
"91porn-server/models/v/dailytaskmod"
|
||
"91porn-server/models/v/exchcodemod"
|
||
"91porn-server/models/v/faqmod"
|
||
"91porn-server/models/v/followmod"
|
||
"91porn-server/models/v/idmod"
|
||
"91porn-server/models/v/ipblockmod"
|
||
"91porn-server/models/v/likemod"
|
||
"91porn-server/models/v/mediamod"
|
||
"91porn-server/models/v/oncetaskmod"
|
||
"91porn-server/models/v/prdcthsomod"
|
||
"91porn-server/models/v/productmod"
|
||
"91porn-server/models/v/proxymod"
|
||
"91porn-server/models/v/signrecordmod"
|
||
"91porn-server/models/v/sourcemod"
|
||
"91porn-server/models/v/sysconfmod"
|
||
"91porn-server/models/v/taskmod"
|
||
"91porn-server/models/v/txnmod"
|
||
"91porn-server/models/v/usermod"
|
||
"91porn-server/models/v/videocoupon"
|
||
"91porn-server/models/v/videodiscountmod"
|
||
"91porn-server/models/v/vidmod"
|
||
"91porn-server/models/v/walletmod"
|
||
"context"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"math"
|
||
"math/rand"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/go-redis/redis"
|
||
"github.com/shopspring/decimal"
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
"go.mongodb.org/mongo-driver/mongo/options"
|
||
)
|
||
|
||
const firstBindPhoneGold = 3
|
||
|
||
func CheckDevIDSign(devID, devToken string) bool {
|
||
if devID == "" || devToken == "" {
|
||
return false
|
||
}
|
||
s := appg.Conf.Base.DevIDSignKey + devID + appg.Conf.Base.DevIDSignKey
|
||
sha := sha256.Sum256([]byte(s))
|
||
bsha := base64.StdEncoding.EncodeToString(sha[:])
|
||
return bsha == devToken
|
||
}
|
||
|
||
// Register 游客注册
|
||
func Register(dc, pc, puc, devID, ip, sysType, devToken, devType, mobile, ver, terminal string, ua ua.UA, tid string) (uid uint64, token string, err error) {
|
||
if devType == "aosp:25" { //疑似刷子使用的devType,正常用户已无,封禁注册
|
||
err = errors.New("invalid")
|
||
return
|
||
}
|
||
uid, err = idmod.GetNextUID()
|
||
if err != nil {
|
||
return
|
||
}
|
||
//默认用户类型
|
||
userType := usermod.UserUnkown
|
||
if terminal == constant.TerminalWeb { //web默认注册为游客
|
||
userType = usermod.UserTourists
|
||
}
|
||
token, err = authuser.GenToken(&authuser.Claims{UID: uid, Timestamp: time.Now().UnixNano(), Type: uint8(userType)})
|
||
if err != nil {
|
||
return
|
||
}
|
||
sysType = strings.ToLower(sysType)
|
||
newPromCode, err := makeOutPromotionCode()
|
||
if err != nil {
|
||
return
|
||
}
|
||
portrait := GetPortrait() //设置默认头像
|
||
name := getName()
|
||
// 广告用户分组
|
||
adGroup := commod.AdGroupNone
|
||
configure, _ := sysconfdata.GetAllFromCache()
|
||
adverAbTestSwitch := configure.GetBool(sysconfmod.VCodeAdverAbTestSwitch)
|
||
if adverAbTestSwitch && (appg.Conf.Base.Env == "test" || (ua.SysType == constant.SysTypeAndroid && adser.VersionCompare(ver, constant.AdGroupClientVersion))) {
|
||
switch uid % 3 {
|
||
case 0:
|
||
adGroup = commod.AdGroupA
|
||
case 1:
|
||
adGroup = commod.AdGroupB
|
||
default:
|
||
adGroup = commod.AdGroupC
|
||
}
|
||
}
|
||
// TODO:根据dc和pc 查询渠道并返回全民代理关系。
|
||
districtInfo := GetUserDistictInfo(dc, pc, ip, sysType)
|
||
u := usermod.User{
|
||
UID: uid,
|
||
DevID: devID,
|
||
Name: name,
|
||
Portrait: portrait,
|
||
RegisterIP: ip,
|
||
WatchCount: sys_config.GetTotalWatchCount(),
|
||
VideoDeduction: 3, //默认视频扣量 30%
|
||
Token: token,
|
||
PromCode: newPromCode,
|
||
DevToken: devToken,
|
||
VipExpireDate: time.Unix(0, 0),
|
||
DevType: devType,
|
||
SysType: common.HandleSysType(sysType),
|
||
Mobile: mobile,
|
||
Ver: ver,
|
||
PUC: puc,
|
||
DiscDoc: commod.NewDiscDoc(getDiscSeqe(districtInfo.DistrictCode, pc), districtInfo.IsDirect, time.Now()),
|
||
LastSysType: common.HandleSysType(sysType),
|
||
Type: userType,
|
||
Tid: tid,
|
||
AdGroup: adGroup,
|
||
OriVer: ver,
|
||
}
|
||
if err = usermod.InsertUser(&u); err != nil {
|
||
return
|
||
}
|
||
if mobile != "" {
|
||
go func() { _ = taskser.CompleteOnceTask(nil, uid, oncetaskmod.OnceTaskTypeBindMobile) }()
|
||
}
|
||
if districtInfo.ParentPromCode != "" {
|
||
authPromotionCode(uid, &districtInfo)
|
||
}
|
||
//Device: ua.SysType,
|
||
// DeviceID: CutTo50(ua.DevID),
|
||
// UserAgent: ua.UserAgent,
|
||
// DeviceModel: ua.DevType,
|
||
|
||
if terminal != constant.TerminalWeb {
|
||
// 数据中心埋点
|
||
userMsg := &datacenter.UserRegisterMsg{
|
||
UserId: int64(u.UID),
|
||
AppId: commod.KFK_APPID,
|
||
SysType: u.SysType,
|
||
DevType: u.DevType,
|
||
Name: u.Name,
|
||
DeviceId: u.DevID,
|
||
IP: ip,
|
||
DistrictCode: u.DistrictCode,
|
||
InviteCode: districtInfo.ParentPromCode,
|
||
RegisterAt: time.Now(),
|
||
UserAgent: ua.BuildID,
|
||
}
|
||
datacenter.AsyncSendMessage(datacenter.TopicUserRegister, userMsg.Marshal())
|
||
}
|
||
redisRegister(uid, sysType, time.Now())
|
||
return
|
||
}
|
||
|
||
func RandName() string {
|
||
return getName()
|
||
}
|
||
|
||
func getName() string {
|
||
const limit = 300
|
||
totalCnt, err := appg.Redis.SCard(redisconst.NameSource)
|
||
if err != nil || totalCnt < limit {
|
||
if len(appg.Name.FamilyNames) == 0 || len(appg.Name.AfterNames) == 0 {
|
||
return "游客" + strconv.FormatInt(time.Now().Unix(), 10)
|
||
}
|
||
for i := 0; i < limit/100; i++ {
|
||
common.Go(func() {
|
||
var names []string
|
||
for j := 0; j < limit/3; j++ {
|
||
length := common.RandInt(2, 4)
|
||
if length == 2 {
|
||
name := appg.Name.FamilyNames[common.RandInt(0, len(appg.Name.FamilyNames))] +
|
||
appg.Name.AfterNames[common.RandInt(0, len(appg.Name.AfterNames))]
|
||
names = append(names, name)
|
||
|
||
}
|
||
if length == 3 {
|
||
name := appg.Name.FamilyNames[common.RandInt(0, len(appg.Name.FamilyNames))] +
|
||
appg.Name.MiddleNames[common.RandInt(0, len(appg.Name.MiddleNames))] +
|
||
appg.Name.AfterNames[common.RandInt(0, len(appg.Name.AfterNames))]
|
||
names = append(names, name)
|
||
}
|
||
}
|
||
_, _ = appg.Redis.SAdd(redisconst.NameSource, names)
|
||
})
|
||
time.Sleep(100 * time.Millisecond)
|
||
}
|
||
_, _ = appg.Redis.ExpireKey(redisconst.NameSource, redisconst.NameSourceExpire)
|
||
}
|
||
name, err := appg.Redis.SPop(redisconst.NameSource)
|
||
if name == "" || err != nil {
|
||
return "游客" + strconv.FormatInt(time.Now().Unix(), 10)
|
||
}
|
||
return name
|
||
}
|
||
|
||
func GetPortrait() string {
|
||
var portrait string
|
||
if portraitLen := len(appg.Static.Portrait); portraitLen > 0 {
|
||
portrait = appg.Static.Portrait[common.RandInt(0, portraitLen)]
|
||
}
|
||
return portrait
|
||
}
|
||
|
||
func getDiscSeqe(dc, pc string) commod.DiscSeqe {
|
||
var ds commod.DiscSeqe
|
||
if dc != "" {
|
||
ds.From(dc)
|
||
}
|
||
ds.PromSeqe = pc
|
||
return ds
|
||
}
|
||
|
||
func redisRegister(uid uint64, sysType string, now time.Time) {
|
||
common.Go(func() { //需要使用 common.Go 以监视携程
|
||
recentMinute := timerange.RecentMinute(now, statrecordmod.FiveMinuteScale)
|
||
rbk := redisconst.RegistBehaviorKey(uid)
|
||
ntf := redisconst.NotifyKey(uid)
|
||
redisKey := redisconst.RegistKey(recentMinute)
|
||
_, _ = appg.Redis.Piplined(func(pipeliner redis.Pipeliner) error {
|
||
pipeliner.SAdd(redisKey, usermod.RegistValue{UID: uid, SysType: sysType})
|
||
pipeliner.Expire(redisKey, redisconst.RegistExpireMax)
|
||
return nil
|
||
})
|
||
_, _ = appg.Redis.Piplined(func(pipeliner redis.Pipeliner) error {
|
||
pipeliner.HMSet(rbk, map[string]interface{}{"default": 1})
|
||
pipeliner.HMSet(ntf, map[string]interface{}{"default": 1})
|
||
pipeliner.Expire(ntf, redisconst.NotifyKeyExpire)
|
||
pipeliner.Expire(rbk, redisconst.NewRegisterBehaExpire)
|
||
return nil
|
||
})
|
||
})
|
||
}
|
||
|
||
// 验证推广码
|
||
func authPromotionCode(uid uint64, inviteInfo *UserDistrictInfo) bool {
|
||
code := proxyser.InviteBind(uid, inviteInfo.ParentPromCode, false)
|
||
// fmt.Println("***authPromotionCode", code)
|
||
log.Info("authPromotionCode", log.Any("uid", uid), log.Any("code", code), log.Any("ParentPromCode", inviteInfo.ParentPromCode), log.Any("ParentInviteCount", inviteInfo.ParentInviteCount))
|
||
return code == stderr.Success
|
||
}
|
||
|
||
// 获取推广吗
|
||
func makeOutPromotionCode() (string, error) {
|
||
redisKey := redisconst.PromotionCodeKey()
|
||
p, _ := appg.Redis.SPop(redisKey)
|
||
if p != "" {
|
||
return p, nil
|
||
}
|
||
return getPromotionCode()
|
||
}
|
||
|
||
// 获取不重复的推广码
|
||
func getPromotionCode() (string, error) {
|
||
promotionCode := common.InvitePromotionCodeGenera()
|
||
u, err := usermod.FindUserPromotionCode(promotionCode)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if u == nil {
|
||
return promotionCode, nil
|
||
}
|
||
return getPromotionCode()
|
||
}
|
||
|
||
// MobileBind 绑定手机号码
|
||
func MobileBind(uid uint64, mobile, passWord string) stderr.Code {
|
||
//手机号是否绑定
|
||
user, err := usermod.FindUserByMobile(mobile)
|
||
if err != nil {
|
||
return stderr.Failure
|
||
}
|
||
if user != nil {
|
||
if user.UID == uid {
|
||
return stderr.ErrMobileHasBind
|
||
}
|
||
return stderr.ErrMobileHasBindByOther
|
||
}
|
||
if err = updateUserMobile(uid, mobile, passWord); err != nil {
|
||
log.Warn("updateUserMobile error", log.E(err))
|
||
return stderr.ErrNetWorkBusy
|
||
}
|
||
go func() { _ = taskser.CompleteOnceTask(nil, uid, oncetaskmod.OnceTaskTypeBindMobile) }()
|
||
return stderr.Success
|
||
}
|
||
|
||
// MobileBind 手机号码登陆
|
||
func MobileLoginOnly(ip, mobile string, ua ua.UA) (*usermod.UserRes, stderr.Code) {
|
||
//手机号是否绑定
|
||
user, err := usermod.FindUserByMobile(mobile)
|
||
if err != nil {
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
if user == nil {
|
||
return nil, stderr.ErrLoginMobileNotExist
|
||
}
|
||
//对马甲账号的合法性做判断,不合法直接视为封禁
|
||
if !IsMaJiaAccount(mobile, user.DevID, user.UID) {
|
||
return nil, stderr.ErrAccessForbid
|
||
}
|
||
if user.HasLocked {
|
||
return &usermod.UserRes{User: *user}, stderr.ErrAccessForbid
|
||
}
|
||
//生产token 刷新token
|
||
token, _ := authuser.GenToken(&authuser.Claims{UID: user.UID, Timestamp: time.Now().UnixNano(), Type: uint8(user.Type)})
|
||
if _, err = usermod.UpdateUserToken(user.UID, token); err != nil {
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
userInfo, err := Info(user.UID)
|
||
if err != nil {
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
userInfo.City = vidhelpser.GetLocationByIP(ip)
|
||
userInfo.Token = token
|
||
authuser.RevokeTokenCache(user.UID)
|
||
|
||
return &userInfo, stderr.Success
|
||
}
|
||
|
||
func lastLogin(uid uint64, token, sys string) error {
|
||
_, err := usermod.Update(uid, usermod.UserSelector{
|
||
Token: &token,
|
||
LastSysType: &sys,
|
||
})
|
||
return err
|
||
}
|
||
|
||
// 默认为都为非马甲账户 不经过校验,只有是马甲账户时才校验合法,
|
||
func IsMaJiaAccount(mobile, devId string, uid uint64) bool {
|
||
struid := strconv.FormatUint(uid, 10)
|
||
return !(mobile[:len(constant.FakeMobilePrefix)] == constant.FakeMobilePrefix && devId != (usermod.SystemDevIDPrex+struid))
|
||
}
|
||
|
||
func updateUserAccount(uid, newUid uint64, account, password string) error {
|
||
if account == "" {
|
||
return errors.New("empty user account")
|
||
}
|
||
user, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if user.Account == account {
|
||
return nil
|
||
}
|
||
if user.AccountBindAt == nil {
|
||
devID := "web-" + user.DevID
|
||
ok, err := usermod.BindAccount(uid, newUid, account, password, devID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !ok {
|
||
return errors.New("bind account failed")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func updateUserMobile(uid uint64, mobile, passWord string) error {
|
||
if mobile == "" {
|
||
return errors.New("mobile is empty")
|
||
}
|
||
newUser, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if newUser.Mobile == mobile {
|
||
return nil
|
||
}
|
||
if newUser.MobileBindAt == nil { //首次绑定
|
||
return appg.VideoDB.Trans(func(t *db.MongoTool) error {
|
||
ok, err := usermod.BindMobile(t, uid, mobile, passWord)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !ok {
|
||
return errors.New("mobile bind faild")
|
||
}
|
||
tl := txnmod.TransactionLog{
|
||
TransNo: primitive.NewObjectID(),
|
||
UID: uid,
|
||
TranType: txnmod.TranType_WLSysGive.Key(),
|
||
TranTypeInt: int64(txnmod.TranType_WLSysGive),
|
||
Desc: fmt.Sprintf("绑定手机赠送: %s游戏金币", strconv.Itoa(firstBindPhoneGold)),
|
||
DiscDoc: newUser.DiscDoc,
|
||
SysType: newUser.SysType,
|
||
Money: decimal.NewFromInt(firstBindPhoneGold),
|
||
}
|
||
return txnmod.InsertTransactionLog(t, &tl)
|
||
})
|
||
}
|
||
ok, err := usermod.ChangeMobile(uid, mobile) //更换手机号
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !ok {
|
||
return errors.New("mobile change faild")
|
||
}
|
||
go func() { _ = taskser.CompleteOnceTask(nil, uid, oncetaskmod.OnceTaskTypeBindMobile) }()
|
||
return nil
|
||
}
|
||
|
||
type CutInfo struct {
|
||
PromotionCode string `json:"pc"`
|
||
DC string `json:"dc"`
|
||
PlatformUserCode string `json:"puc"`
|
||
Tid string `json:"tid"`
|
||
}
|
||
|
||
func (this *CutInfo) IsEmpty() bool {
|
||
return this.PromotionCode == "" && this.DC == ""
|
||
}
|
||
|
||
// DeviceLogin () 设备登陆
|
||
func DeviceLogin(ip string, p usermod.DevLoginP, token string, ua ua.UA) (userInfo usermod.UserRes, code stderr.Code, loginType int, err error) {
|
||
// iOS 开头的 devType(如 "iOS:25.3.0")统一规范化为 "ios"
|
||
if strings.HasPrefix(strings.ToLower(p.DevType), "ios") {
|
||
p.DevType = "ios"
|
||
}
|
||
var u *usermod.User
|
||
var isRegister bool
|
||
var uid uint64
|
||
if token != "" {
|
||
u, _ = usermod.FindUserByToken(token)
|
||
if u != nil {
|
||
uid = u.UID
|
||
loginType = usermod.TokenLogin
|
||
}
|
||
}
|
||
devID := p.DevID
|
||
if p.QrCnt != "" {
|
||
devID, err = GetDevIDFromQrCnt(p.QrCnt)
|
||
if err != nil {
|
||
switch err.(type) {
|
||
case ysqr.ErrContentInvalid:
|
||
code = stderr.ErrQrInvalid
|
||
default:
|
||
code = stderr.ErrNetWorkBusy
|
||
}
|
||
return
|
||
}
|
||
loginType = usermod.QrCodeLogin
|
||
}
|
||
if u == nil {
|
||
u := &usermod.User{}
|
||
if devID != "" {
|
||
u, _ = usermod.FindUserByDevID(devID)
|
||
} else {
|
||
code = stderr.ErrDeviceIdAndMobileNil
|
||
err = errors.New("devID and mobile is nil")
|
||
return
|
||
}
|
||
if u == nil {
|
||
dc, pc, puc, tid := parseCutInfo(p.CutInfos, ip, p.SysType)
|
||
ver, err1 := version.New(p.Ver)
|
||
if err1 == nil && ver.GTE(version.MustNew(constant.Ver3_6_0)) {
|
||
if !CheckDevIDSign(devID, p.DevToken) {
|
||
code = stderr.ErrBadDevID
|
||
err = errors.New("bad args d")
|
||
log.Info("DeviceLogin Register Bad DevID", log.Any("devLogin", p), log.Any("ip", ip))
|
||
return
|
||
}
|
||
}
|
||
// 单 IP 注册总量闸:与 H5Login 共用 register:quota:{ip} 计数,建号前先判定——24h 滚动窗口内
|
||
// 同一真实 IP 注册数超额即拒绝并拉黑。放在设备签名校验之后,避免坏 devID 请求白白消耗配额。
|
||
// 注意:LoginWithDevice 仅在 err != nil 时才读 code,故这里须与其它拒绝分支一样同时置 err。
|
||
if code = checkRegisterQuota(ip); code != stderr.Success {
|
||
err = errors.New(code.Msg())
|
||
return
|
||
}
|
||
log.Info("DeviceLogin Register info", log.Any("devLogin", p), log.Any("ip", ip))
|
||
uid, token, err = Register(dc, pc, puc, devID, ip, p.SysType, p.DevToken, p.DevType, p.Mobile, p.Ver, constant.TerminalAndroid, ua, tid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
isRegister = true
|
||
} else {
|
||
//切换账号 刷新token
|
||
token, _ = authuser.GenToken(&authuser.Claims{UID: u.UID, Timestamp: time.Now().UnixNano(), Type: uint8(u.Type)})
|
||
uid = u.UID
|
||
}
|
||
}
|
||
if userInfo, err = Info(uid); err != nil {
|
||
return
|
||
}
|
||
//禁止登陆
|
||
if userInfo.HasLocked {
|
||
err = errors.New(stderr.ErrAccessForbid.Msg())
|
||
code = stderr.ErrAccessForbid
|
||
return
|
||
}
|
||
if err = lastLogin(uid, token, p.SysType); err != nil {
|
||
return
|
||
}
|
||
userInfo.City = vidhelpser.GetLocationByIP(ip)
|
||
userInfo.Token = token
|
||
userInfo.IsRegister = isRegister
|
||
authuser.RevokeTokenCache(uid)
|
||
|
||
return
|
||
}
|
||
|
||
func parseCutInfo(cutInfo, ip, sysType string) (dc, pc, puc, tid string) {
|
||
var _cutInfo CutInfo //获取推广码/商区
|
||
_ = json.Unmarshal([]byte(cutInfo), &_cutInfo)
|
||
log.Info("deviceLogin parseCutInfo info", log.Any("cutInfo", _cutInfo), log.Any("ip", ip), log.Any("systype", sysType))
|
||
dc = _cutInfo.DC
|
||
pc = _cutInfo.PromotionCode
|
||
puc = _cutInfo.PlatformUserCode
|
||
tid = _cutInfo.Tid
|
||
return
|
||
}
|
||
|
||
func GetDevIDFromQrCnt(qrCnt string) (string, error) {
|
||
uid, err := ysqr.GetUIDFromQrCnt(ysqr.Login, qrCnt)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
user, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if user == nil {
|
||
return "", ysqr.ErrContentInvalid{}
|
||
}
|
||
c := ysqr.Content{
|
||
UID: user.UID,
|
||
T: ysqr.Login,
|
||
LoginClaims: ysqr.LoginClaims{
|
||
DevID: user.DevID,
|
||
},
|
||
}
|
||
if qrCnt != c.String(authuser.GetTokenSecret()) {
|
||
return "", ysqr.ErrContentInvalid{}
|
||
}
|
||
return user.DevID, nil
|
||
}
|
||
|
||
// GetUIDFromQrCnt 校验用户凭证二维码;获取用户uid
|
||
func GetUIDFromQrCnt(content string) (uid uint64, err error) {
|
||
uid, err = ysqr.GetUIDFromQrCnt(ysqr.Login, content)
|
||
if err != nil {
|
||
return
|
||
}
|
||
user, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if user == nil {
|
||
err = ysqr.ErrInvalidUser
|
||
return
|
||
}
|
||
c := ysqr.Content{
|
||
UID: user.UID,
|
||
T: ysqr.Login,
|
||
LoginClaims: ysqr.LoginClaims{
|
||
DevID: user.DevID,
|
||
},
|
||
}
|
||
if content != c.String(authuser.GetTokenSecret()) {
|
||
err = ysqr.ErrContentIllegal
|
||
return
|
||
}
|
||
return uid, nil
|
||
}
|
||
|
||
func MyRights(uid uint64) (rights usermod.UserRights, err error) {
|
||
coupons, err := videocoupon.GetCouponsByUID(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
user, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
cards, err := getMyCards(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
info, err := Info(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
rights.Cards = cards
|
||
rights.UID = uid
|
||
rights.GoldVideoCoupons = MergeGoldVideoCoupons(user, coupons)
|
||
rights.GoldVideoDiscount = info.PayVidDiscount
|
||
rights.VipExpireDate = info.VipExpireDate
|
||
rights.GoldVideoFreeExpire = info.GoldVideoFreeExpire
|
||
return
|
||
}
|
||
|
||
func getMyCards(uid uint64) (cards []usermod.Card, err error) {
|
||
histories, err := prdcthsomod.FindUserProductHistory(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
productIDs := make([]primitive.ObjectID, len(histories))
|
||
for i, h := range histories {
|
||
productIDs[i] = h.ProductID
|
||
}
|
||
proucts, err := productmod.FindByProductIDs(productIDs)
|
||
if err != nil {
|
||
return
|
||
}
|
||
productMap := make(map[primitive.ObjectID]productmod.Product)
|
||
for _, p := range proucts {
|
||
productMap[p.ID] = p
|
||
}
|
||
now := time.Now()
|
||
protuctTimeMap := make(map[primitive.ObjectID]time.Duration)
|
||
for _, h := range histories {
|
||
if h.ProductType != productmod.VIP {
|
||
expiration := h.CreatedAt.AddDate(0, 0, productMap[h.ProductID].Duration)
|
||
if expiration.After(now) {
|
||
duration, ok := protuctTimeMap[h.ProductID]
|
||
if !ok {
|
||
protuctTimeMap[h.ProductID] = expiration.Sub(now)
|
||
} else {
|
||
protuctTimeMap[h.ProductID] = duration + expiration.Sub(now)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
cards = make([]usermod.Card, len(protuctTimeMap))
|
||
i := 0
|
||
for id, d := range protuctTimeMap {
|
||
cards[i] = usermod.Card{
|
||
Name: productMap[id].Name,
|
||
Expiration: now.Add(d),
|
||
}
|
||
i++
|
||
}
|
||
return
|
||
}
|
||
|
||
// Info () 我的信息
|
||
func Info(uid uint64) (usermod.UserRes, error) {
|
||
var u *usermod.User
|
||
var vT int64
|
||
var promoteURL string // 邀请地址
|
||
var inviterCode string // 邀请码
|
||
var buyVidCount int64 // 用户购买的视频数
|
||
var rchgLevel usermod.RechargeLevel // 充值等级
|
||
var videoDiscountLog videodiscountmod.VideoDiscountLog // 视频折扣卡
|
||
var videoCoupons []videocoupon.UserGoldVideoCoupon // 新版观影券
|
||
var whoringCardInfo usermod.WhoringCardInfo // 白嫖卡信息
|
||
var payVidDisc int // 视频折扣
|
||
var isPopUp bool // 是否弹窗
|
||
var totalInvite int64 // 累计邀请人数
|
||
u, err := usermod.FindUserByUID(uid)
|
||
if err != nil || u == nil {
|
||
return usermod.UserRes{}, err
|
||
}
|
||
correctGoldVideoFreeExpire(u)
|
||
if u.SnapVip {
|
||
elog, err := exchlogmod.GetLogByUIDAndAuthority(u.UID, exchcodemod.Authority3dPermanentVIP)
|
||
if err != nil {
|
||
return usermod.UserRes{}, err
|
||
}
|
||
if elog.CreatedAt.Add(time.Hour * 24).Before(time.Now()) {
|
||
s := 0
|
||
u, err = usermod.Update(u.UID, usermod.UserSelector{SnapVip: &s, VipExpireDate: u.OriginVipExpire, VipLevel: u.OriginVipLevel})
|
||
if err != nil {
|
||
return usermod.UserRes{}, err
|
||
}
|
||
}
|
||
}
|
||
var userInfo usermod.UserRes
|
||
if u.HasWhoringCard {
|
||
checkTodayLoginSign(uid)
|
||
}
|
||
var isUpgrade bool // 是否vip升级
|
||
var wg sync.WaitGroup
|
||
wg.Add(10)
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
status := true
|
||
// 获取会员卡列表
|
||
productList, _ := productmod.FindProductsByProductType(&status, false, "")
|
||
// 根据用户当前卡进行升级
|
||
productser.CheckUserUpgradeProducts(uid, productList)
|
||
for _, v := range productList {
|
||
if v.IsUpgrade == true {
|
||
isUpgrade = true
|
||
return
|
||
}
|
||
}
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
userInfo.User = *u
|
||
if u.VipExpireDate.After(time.Now()) {
|
||
userInfo.IsVIP = true
|
||
} else {
|
||
if u.VipLevel > 0 {
|
||
_, err = usermod.UpdateUserVipLevel(uid, 0)
|
||
if err != nil {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
userInfo.Age = u.Age()
|
||
userInfo.Token = ""
|
||
|
||
userInfo.ShowProxy = true
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
ivt, err := proxymod.GetInvrLv1(uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if ivt == 0 {
|
||
return
|
||
}
|
||
u, err := usermod.FindUserByUID(ivt)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if u != nil {
|
||
inviterCode = u.PromCode
|
||
}
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
promoteURL = sourcemod.GetRandomPromotionURL()
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
buyVidCount, _ = countPay4VidLogByUID(uid, "SP")
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
totalInvite, err = proxymod.FindTotal(uid)
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
videoDiscountLog, err = videodiscountmod.GetByUID(uid)
|
||
if videoDiscountLog.Expiration.After(time.Now()) && u.PayVidDiscount > videoDiscountLog.VideoDiscount {
|
||
payVidDisc = videoDiscountLog.VideoDiscount
|
||
} else {
|
||
payVidDisc = u.PayVidDiscount
|
||
}
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
videoCoupons, err = videocoupon.GetCouponsByUID(uid)
|
||
if err != nil {
|
||
log.Error("Info videocoupon.GetCouponsByUID", log.Any("uid", uid), log.E(err))
|
||
}
|
||
})
|
||
//判断用户今日是否领取了金币
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
if u.CoinMouthExpireDate.After(time.Now()) {
|
||
now := time.Now()
|
||
startTime := timeutil.BeginOfTime(now)
|
||
endTime := timeutil.BeginningOfTomorrow(now)
|
||
filter := bson.M{"uid": uid, "createdAt": bson.M{"$gte": startTime, "$lt": endTime}, "desc": bson.M{"$in": []string{"官方充值-购买[金币月卡]", "金币月卡每日赠送金币"}}}
|
||
opts := options.Find()
|
||
count, _, _ := txnmod.FindTransactionLogs(filter, opts)
|
||
if count < 1 {
|
||
isPopUp = true
|
||
}
|
||
}
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
resp := []usermod.DetailInfo{}
|
||
if u.HasWhoringCard && u.VipExpireDate.After(time.Now()) {
|
||
// 获取用户的打卡记录信息
|
||
whoringCardInfo.HasWhoringCard = u.HasWhoringCard
|
||
whoringCardInfo.DetailInfo = getSignInfo(uid)
|
||
} else {
|
||
whoringCardInfo.DetailInfo = resp
|
||
}
|
||
userInfo.WhoringCard = whoringCardInfo
|
||
})
|
||
wg.Wait()
|
||
userInfo.IsUpgrade = isUpgrade
|
||
userInfo.IsPopUps = isPopUp
|
||
userInfo.TotalInvite = totalInvite
|
||
userInfo.CollectionCount = vT
|
||
userInfo.Follows = u.Follows
|
||
userInfo.Fans = u.Fans
|
||
userInfo.UpTag = u.UpTag
|
||
userInfo.VipName = u.VipName
|
||
userInfo.PromoteURL = common.BindUrl(promoteURL, constant.PromotionField+u.PromCode)
|
||
userInfo.InviterCode = inviterCode
|
||
userInfo.BuyVidCount = buyVidCount
|
||
userInfo.RechargeLevel = rchgLevel
|
||
userInfo.VideoDiscountExpiration = videoDiscountLog.Expiration
|
||
userInfo.GoldVideoCoupon = Modify(videoCoupons, userInfo.GoldVideoCoupon)
|
||
userInfo.PayVidDiscount = payVidDisc // 取用户最大视频折扣
|
||
userInfo.GoldVideoFreeExpire = u.GoldVideoFreeExpire
|
||
userInfo.BroadcastExpire = u.BroadcastExpire
|
||
userInfo.OriVer = u.OriVer
|
||
userInfo.AdGroup = u.AdGroup
|
||
// 仅当用户参与广告 AB 测(安卓 + 已分组 + 注册版本号 >= AB 测版本号)时才下发展示类型,
|
||
// 与广告统计口径 adser.CanRecord 保持一致,避免老版本(注册版本低于门槛)用户误命中。
|
||
if adser.CanRecord(u) {
|
||
configure, _ := sysconfdata.GetAllFromCache()
|
||
abTestShowTypeMap := configure.GetObject(sysconfmod.VCodeAdverAbTestShowType)
|
||
if showType, ok := abTestShowTypeMap[string(u.AdGroup)]; ok {
|
||
userInfo.AdverAbTestShowType, _ = strconv.Atoi(showType)
|
||
}
|
||
}
|
||
userInfo.SendMsgPrice = messageser.CheckChatPrice(u)
|
||
puc := DLInfo{
|
||
1,
|
||
u.UID,
|
||
}
|
||
tempM := make(map[string]string)
|
||
tempM["puc"], _ = puc.Enc()
|
||
b, _ := json.Marshal(tempM)
|
||
userInfo.AppStoreCode = string(b)
|
||
//webToken
|
||
now := time.Now()
|
||
expiresAt := now.Add(time.Hour * 24 * 7)
|
||
webToken, _ := authuser.GenWebToken(authuser.WebClaims{
|
||
UID: uid,
|
||
IssuedAt: now.Unix(),
|
||
ExpiresAt: expiresAt.Unix(),
|
||
})
|
||
userInfo.WebToken = webToken
|
||
|
||
common.Go(func() {
|
||
_ = taskser.CompleteDailyTask(nil, uid, dailytaskmod.DailyUserLogin)
|
||
// 连续登陆-成长任务
|
||
taskser.CompleteGrowthTask(uid, taskmod.GrowthBuyLogin, userInfo.LoginDays)
|
||
})
|
||
|
||
attachImToken(&userInfo)
|
||
return userInfo, nil
|
||
}
|
||
|
||
// attachImToken 给用户信息附加 IM SDK 登录凭据
|
||
func attachImToken(userInfo *usermod.UserRes) {
|
||
if userInfo == nil || userInfo.UID == 0 {
|
||
return
|
||
}
|
||
userInfo.ImEnabled = imser.SDKEnabled()
|
||
if !userInfo.ImEnabled {
|
||
return
|
||
}
|
||
authInfo, err := imser.GetSDKAuth(userInfo.UID)
|
||
if err != nil {
|
||
log.Warn("attach im token failed", log.Any("uid", userInfo.UID), log.E(err))
|
||
return
|
||
}
|
||
userInfo.ImToken = authInfo.ImToken
|
||
userInfo.ImUserID = authInfo.ImUserID
|
||
userInfo.ImDynamicDomain = authInfo.DynamicConfigDomain
|
||
userInfo.ImSocketURL = authInfo.SocketURL
|
||
}
|
||
|
||
func getSignInfo(uid uint64) (data []usermod.DetailInfo) {
|
||
resp := []usermod.DetailInfo{}
|
||
// 获取用户的打卡记录信息
|
||
records, err := signrecordmod.GetInfoByUID(uid)
|
||
if err != nil {
|
||
return resp
|
||
}
|
||
|
||
if len(records) > 0 {
|
||
rts := []usermod.Record{}
|
||
for i := range records {
|
||
if len(records[i].RecordTime) > 0 {
|
||
for _, r := range records[i].RecordTime {
|
||
rts = append(rts, usermod.Record{
|
||
SignTime: r.SignTime,
|
||
IsSign: r.IsSign,
|
||
})
|
||
}
|
||
}
|
||
var price int64
|
||
if records[i].ForgetSignDays > 0 {
|
||
for p := 0; p < int(records[i].ForgetSignDays+records[i].RenewalSignDays); p++ {
|
||
if p < int(records[i].RenewalSignDays) {
|
||
continue
|
||
}
|
||
price += int64(math.Pow(2, float64(p)) * 10)
|
||
}
|
||
}
|
||
|
||
resp = append(resp, usermod.DetailInfo{
|
||
ID: records[i].ID,
|
||
TotalDays: records[i].TotalDays,
|
||
CurrentSignDays: records[i].CurrentSignDays,
|
||
RenewalSignDays: records[i].RenewalSignDays,
|
||
RenewalSignPrice: price,
|
||
ForgetSignDays: records[i].ForgetSignDays,
|
||
HasReturn: records[i].HasReturn,
|
||
RecordTime: rts,
|
||
StartTime: records[i].CreatedAt,
|
||
EndTime: records[i].EndTime,
|
||
})
|
||
}
|
||
}
|
||
|
||
return resp
|
||
}
|
||
|
||
func checkTodayLoginSign(uid uint64) error {
|
||
// 获取是否有白嫖卡任务
|
||
signRecord, err := signrecordmod.GetInfoByCond(bson.M{"uid": uid})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if signRecord == nil || signRecord.ID.IsZero() {
|
||
return errors.New("sign record is null")
|
||
}
|
||
currentTime := time.Now()
|
||
|
||
if signRecord.CurrentSignDays == signRecord.TotalDays {
|
||
return nil
|
||
}
|
||
totalDays := signRecord.CurrentSignDays + signRecord.ForgetSignDays
|
||
if totalDays >= signRecord.TotalDays {
|
||
return nil
|
||
}
|
||
|
||
// 判断今日是否登录
|
||
today := timeutil.BeginningOfDay(time.Now())
|
||
if signRecord.SignTime.Before(today) && signRecord.EndTime.After(currentTime) {
|
||
var isReturn bool
|
||
var isReturnAmt int64
|
||
|
||
// 打卡
|
||
update := bson.M{"signTime": currentTime, "currentSignDays": signRecord.CurrentSignDays + 1}
|
||
|
||
// 处理是否漏打卡
|
||
duration := today.Sub(signRecord.SignTime)
|
||
days := int64(duration.Hours() / 24)
|
||
|
||
totalForgetSignDays := signRecord.ForgetSignDays + days
|
||
if days > 0 {
|
||
// 漏打卡业务
|
||
rec := signRecord.RecordTime
|
||
for i := 1; i <= int(days); i++ {
|
||
format := currentTime.AddDate(0, 0, -i).Format("2006-01-02")
|
||
details := signrecordmod.Record{
|
||
SignTime: format,
|
||
IsSign: false,
|
||
}
|
||
rec = append(rec, details)
|
||
}
|
||
|
||
update["recordTime"] = rec
|
||
update["forgetSignDays"] = totalForgetSignDays
|
||
}
|
||
if totalForgetSignDays+signRecord.CurrentSignDays+1 > signRecord.TotalDays {
|
||
return nil
|
||
}
|
||
|
||
if signRecord.CurrentSignDays+1 == signRecord.TotalDays {
|
||
isReturn = true
|
||
update["hasReturn"] = isReturn
|
||
// 发放奖励
|
||
product, err := productmod.FindOne(nil, bson.M{"_id": signRecord.PID}, options.FindOne())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if product == nil || product.ID.IsZero() {
|
||
return errors.New("product is null")
|
||
}
|
||
isReturnAmt = *product.DiscountedPriceIos
|
||
}
|
||
|
||
if err = appg.VideoDB.Trans(func(tool *db.MongoTool) error {
|
||
_, err := signrecordmod.UpdateByCond(tool, bson.M{"_id": signRecord.ID}, update)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 补签打卡扣除金币
|
||
var txnLogs []txnmod.TransactionLog
|
||
if isReturn {
|
||
wallAct, err := walletmod.CreditAmount(tool, isReturnAmt, uid)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
tl := txnmod.TransactionLog{
|
||
TransNo: primitive.NewObjectID(),
|
||
UID: uid,
|
||
Amount: isReturnAmt,
|
||
ActualAmount: float64(isReturnAmt),
|
||
TranType: txnmod.SuccessSignReturnAmount.Key(),
|
||
TranTypeInt: int64(txnmod.SuccessSignReturnAmount),
|
||
Desc: fmt.Sprintf("%v:%v个", txnmod.SuccessSignReturnAmount.Key(), isReturnAmt),
|
||
RealAmount: wallAct.RealAmount(),
|
||
}
|
||
txnLogs = append(txnLogs, tl)
|
||
}
|
||
if len(txnLogs) > 0 {
|
||
err := txnmod.InsertManyTransactionLog(tool, txnLogs)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}); err != nil {
|
||
log.Error(fmt.Sprintf("uid:%v, login sign Trans err:%v", uid, err))
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 如果签到过期
|
||
if signRecord.EndTime.Before(currentTime) && totalDays < signRecord.TotalDays {
|
||
// 打卡
|
||
update := bson.M{"signTime": signRecord.EndTime}
|
||
|
||
// 处理是否漏打卡
|
||
days := signRecord.TotalDays - signRecord.ForgetSignDays - signRecord.CurrentSignDays
|
||
|
||
totalForgetSignDays := signRecord.ForgetSignDays + days
|
||
if days > 0 {
|
||
// 漏打卡业务
|
||
rec := signRecord.RecordTime
|
||
for i := 0; i < int(days); i++ {
|
||
format := signRecord.EndTime.AddDate(0, 0, -i).Format("2006-01-02")
|
||
details := signrecordmod.Record{
|
||
SignTime: format,
|
||
IsSign: false,
|
||
}
|
||
rec = append(rec, details)
|
||
}
|
||
|
||
update["recordTime"] = rec
|
||
update["forgetSignDays"] = totalForgetSignDays
|
||
}
|
||
if totalForgetSignDays+signRecord.CurrentSignDays > signRecord.TotalDays {
|
||
return nil
|
||
}
|
||
|
||
_, err := signrecordmod.UpdateByCond(nil, bson.M{"_id": signRecord.ID}, update)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func correctGoldVideoFreeExpire(user *usermod.User) {
|
||
if user.VideoFreeExpiration != nil && user.VideoFreeExpiration.After(user.GoldVideoFreeExpire) {
|
||
user.GoldVideoFreeExpire = *user.VideoFreeExpiration
|
||
}
|
||
}
|
||
|
||
// Modify 为非H5端修正数据,合并新版数据
|
||
func Modify(videoCoupons []videocoupon.UserGoldVideoCoupon, oldCoupons []usermod.UserGoldVideoCoupon) []usermod.UserGoldVideoCoupon {
|
||
for _, new := range videoCoupons {
|
||
if !new.Used {
|
||
isNewNum := true
|
||
for i, old := range oldCoupons {
|
||
if old.Gold == new.Num {
|
||
oldCoupons[i].Count++
|
||
isNewNum = false
|
||
break
|
||
}
|
||
}
|
||
if isNewNum {
|
||
oldCoupons = append(oldCoupons, usermod.UserGoldVideoCoupon{
|
||
Gold: new.Num,
|
||
Count: 1,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
return oldCoupons
|
||
}
|
||
|
||
// MergeGoldVideoCoupons 合并新旧版观影券,方便前端展示
|
||
func MergeGoldVideoCoupons(u *usermod.User, coupons []videocoupon.UserGoldVideoCoupon) []usermod.GoldVideoCoupon {
|
||
unusedCouponDetails := []usermod.GoldVideoCouponDetail{}
|
||
for _, v := range u.GoldVideoCoupon { // 旧版数据均为未使用的
|
||
unusedCouponDetails = append(unusedCouponDetails, usermod.GoldVideoCouponDetail{
|
||
Num: v.Gold,
|
||
Count: v.Count,
|
||
Source: videocoupon.GoldVideoCouponSourceVIP,
|
||
})
|
||
}
|
||
unusedCoupons := usermod.GoldVideoCoupon{
|
||
Position: usermod.CouponPositionUnUsed,
|
||
Coupons: unusedCouponDetails,
|
||
}
|
||
usedCoupons := usermod.GoldVideoCoupon{Position: usermod.CouponPositionUsed,
|
||
Coupons: []usermod.GoldVideoCouponDetail{}}
|
||
for _, v := range coupons { // 新版观影券
|
||
if !v.Used { // 观影券尚未使用
|
||
isNew := true
|
||
for i, coupon := range unusedCoupons.Coupons {
|
||
if v.Num == coupon.Num {
|
||
unusedCoupons.Coupons[i].Count++
|
||
isNew = false
|
||
break
|
||
}
|
||
}
|
||
if isNew {
|
||
unusedCoupons.Coupons = append(unusedCoupons.Coupons, usermod.GoldVideoCouponDetail{
|
||
Num: v.Num,
|
||
Count: 1,
|
||
Source: v.Source,
|
||
})
|
||
}
|
||
} else { // 观影券已被使用
|
||
isNew := true
|
||
for i, coupon := range usedCoupons.Coupons {
|
||
if v.Num == coupon.Num {
|
||
usedCoupons.Coupons[i].Count++
|
||
isNew = false
|
||
break
|
||
}
|
||
}
|
||
if isNew {
|
||
usedCoupons.Coupons = append(usedCoupons.Coupons, usermod.GoldVideoCouponDetail{
|
||
Num: v.Num,
|
||
Count: 1,
|
||
Source: v.Source,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
ret := []usermod.GoldVideoCoupon{unusedCoupons, usedCoupons}
|
||
return ret
|
||
}
|
||
|
||
// OtherInfo () 他的信息
|
||
func OtherInfo(iID uint64, hID uint64) (userInfo usermod.UserRes, err error) {
|
||
var u *usermod.User
|
||
//var vT int64
|
||
var buyVidCount int64 //用户购买的视频数
|
||
var rchgLevel usermod.RechargeLevel //充值等级
|
||
var videoDiscountLog videodiscountmod.VideoDiscountLog //视频折扣卡
|
||
var isFollow bool
|
||
u, err = usermod.FindUserByUID(hID)
|
||
if u == nil || err != nil {
|
||
return
|
||
}
|
||
if u.SnapVip {
|
||
var elog exchlogmod.ExchangeLog
|
||
elog, err = exchlogmod.GetLogByUIDAndAuthority(u.UID, exchcodemod.Authority3dPermanentVIP)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if elog.CreatedAt.Add(time.Hour * 24).Before(time.Now()) {
|
||
s := 0
|
||
u, err = usermod.Update(u.UID, usermod.UserSelector{SnapVip: &s, VipExpireDate: u.OriginVipExpire, VipLevel: u.OriginVipLevel})
|
||
if err != nil {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
var wg sync.WaitGroup
|
||
wg.Add(5)
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
isFollow, _ = followmod.IsFollow(iID, hID)
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
userInfo.User = *u
|
||
if u.VipExpireDate.After(time.Now()) {
|
||
userInfo.IsVIP = true
|
||
} else {
|
||
if u.VipLevel > 0 {
|
||
_, err = usermod.UpdateUserVipLevel(hID, 0)
|
||
if err != nil {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
userInfo.Age = u.Age()
|
||
userInfo.Token = ""
|
||
})
|
||
//common.Go(func() {
|
||
// defer wg.Done()
|
||
// vT, _ = vidmod.GetPublishedVideoCntByPublisher(hID)
|
||
//})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
buyVidCount, _ = countPay4VidLogByUID(hID, "SearchSP")
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
rchgLevel, _ = walletser.GetRchgLevel(hID)
|
||
})
|
||
common.Go(func() {
|
||
defer wg.Done()
|
||
videoDiscountLog, err = videodiscountmod.GetByUID(iID)
|
||
if err != nil {
|
||
log.Error("videodiscountmod.GetByUID", log.Any("iID", iID), log.Any("hID", hID), log.E(err))
|
||
}
|
||
})
|
||
//common.Go(func() {
|
||
// defer wg.Done()
|
||
// userInfo.HappinessPlazaCount, _ = vidmod.GetVideoCntByPublisher(hID, vidmod.COVER)
|
||
//})
|
||
wg.Wait()
|
||
userInfo.Fans = u.Fans
|
||
userInfo.Follows = u.Follows
|
||
userInfo.UpTag = u.UpTag
|
||
userInfo.CollectionCount = u.TotalWorks
|
||
userInfo.BuyVidCount = buyVidCount
|
||
userInfo.RechargeLevel = rchgLevel
|
||
userInfo.VideoDiscountExpiration = videoDiscountLog.Expiration
|
||
userInfo.IsFollow = isFollow
|
||
return
|
||
}
|
||
|
||
// BuyVidList () 购买视频列表
|
||
func BuyVidList(uid, oid uint64, newsType string, pageSize int64, pageNumber int64) (data []*vidmod.VideoInfoResp, total int64, hasNext bool, err error) {
|
||
if oid == 0 {
|
||
oid = uid
|
||
}
|
||
//var wg sync.WaitGroup
|
||
//wg.Add(2)
|
||
//common.Go(func() {
|
||
//defer wg.Done()
|
||
var vl []*payvidlgmod.Pay4VidLog
|
||
vl, hasNext, err = payvidlgmod.FindByUID(oid, newsType, pageNumber, pageSize)
|
||
if err != nil {
|
||
return
|
||
}
|
||
keyFunc := func(vid string, uid uint64) string {
|
||
return fmt.Sprintf("%s-%d", vid, uid)
|
||
}
|
||
|
||
vlCoinsMap := make(map[string]int64)
|
||
for _, v := range vl {
|
||
if v != nil {
|
||
vlCoinsMap[keyFunc(v.VideoID.Hex(), v.UID)] = v.Coins
|
||
}
|
||
}
|
||
ids := make([]primitive.ObjectID, 0)
|
||
for _, v := range vl {
|
||
ids = append(ids, v.VideoID)
|
||
}
|
||
data = vidhelpser.GetVideoListByIDsNoStatus(uid, ids)
|
||
if oid == uid { // 仅当用户查看自己的购买列表时才展示购买时的金币数
|
||
for i, v := range data {
|
||
if v != nil {
|
||
data[i].Coins = vlCoinsMap[keyFunc(v.ID.Hex(), oid)]
|
||
}
|
||
}
|
||
}
|
||
//})
|
||
//common.Go(func() {
|
||
// defer wg.Done()
|
||
// total, _ = countPay4VidLogByUID(oid, newsType)
|
||
//})
|
||
//wg.Wait()
|
||
return
|
||
}
|
||
|
||
// GetUserLikeReq
|
||
type GetUserLikeReq struct {
|
||
commod.Page
|
||
UID uint64 `json:"uid" form:"uid"` // 查看那个用户,自己不传
|
||
LikeType string `json:"likeType" form:"likeType"` // 点赞类型: SP/SHORT/COVER/PIC/SEED_LINK 或 video/image/text/drama
|
||
}
|
||
|
||
// UserLikesVidList 用户喜欢的作品列表
|
||
func UserLikesVidList(uid uint64, req *GetUserLikeReq) (vList any, hasNext bool, err error) {
|
||
if req.UID == 0 {
|
||
req.UID = uid
|
||
}
|
||
//var wg sync.WaitGroup
|
||
//wg.Add(2)
|
||
//common.Go(func() {
|
||
// defer wg.Done()
|
||
var likeList []likemod.LikeOIDRes
|
||
likeList, hasNext, err = likemod.FindLikesByTypeAndUID(req.LikeType, req.UID, int(req.PageNumber), int(req.PageSize))
|
||
var vids []primitive.ObjectID
|
||
for _, v := range likeList {
|
||
vids = append(vids, v.ObjID)
|
||
}
|
||
if len(vids) <= 0 {
|
||
vList = make([]*vidmod.VideoInfoResp, 0)
|
||
return
|
||
}
|
||
// 区分普通帖子和ACG
|
||
switch req.LikeType {
|
||
case constant.AiPlaza:
|
||
// 获取ai广场帖子
|
||
list, err := aiplazamod.GetAll(bson.M{"_id": bson.M{"$in": vids}}, nil)
|
||
if err != nil {
|
||
return vList, hasNext, err
|
||
}
|
||
// 按照点赞时间排序重新排排序
|
||
dataMap := make(map[primitive.ObjectID]aiplazamod.AiPlaza)
|
||
for _, v := range list {
|
||
dataMap[v.ID] = v
|
||
}
|
||
infos := []aiplazamod.AiPlaza{}
|
||
for _, v := range likeList {
|
||
video, ok := dataMap[v.ObjID]
|
||
if !ok {
|
||
continue
|
||
}
|
||
infos = append(infos, video)
|
||
}
|
||
vList = aiplazadata.FormatAppDataList(infos)
|
||
case constant.LikeTypeSP, constant.LikeTypeShort, constant.LikeTypeCover, constant.LikeTypePic, constant.LikeTypeSEED_LINK:
|
||
videos, err := vidmod.GetVideoListByIDs(vids)
|
||
if err != nil {
|
||
return vList, hasNext, err
|
||
}
|
||
//修正用户喜欢的记录 如果此时vList为0 表示用户喜欢的视频 有大量的发生了状态改变,此时需要对记录表进行修正, 删除掉视频状态不为1的喜欢记录
|
||
if len(videos) == 0 {
|
||
common.Go(func() {
|
||
vidhelpser.FixLikeVideoRecord(uid, vids)
|
||
})
|
||
}
|
||
// 按照点赞时间排序重新排排序
|
||
videoMap := make(map[primitive.ObjectID]*vidmod.VideoModel)
|
||
for _, v := range videos {
|
||
videoMap[v.ID] = v
|
||
}
|
||
infos := []*vidmod.VideoModel{}
|
||
for _, v := range likeList {
|
||
video, ok := videoMap[v.ObjID]
|
||
if !ok {
|
||
continue
|
||
}
|
||
infos = append(infos, video)
|
||
}
|
||
vList = vidhelpser.NewEncodeVideoInfo(uid, infos)
|
||
|
||
case constant.LikeTypeCartoon, constant.LikeTypeComics, constant.LikeTypeText, constant.LikeTypeDrama:
|
||
_, m, err := mediamod.GetListByIds(vids)
|
||
if err != nil {
|
||
return vList, hasNext, err
|
||
}
|
||
// 按照点赞时间排序重新排排序
|
||
infos := []*mediamod.Media{}
|
||
for _, like := range likeList {
|
||
if media, ok := m[like.ObjID]; ok {
|
||
if req.LikeType == constant.LikeTypeDrama && (media.MediaType != mediamod.MediaTypeDrama || media.Status != 1 || media.IsDelete) {
|
||
continue
|
||
}
|
||
infos = append(infos, &media)
|
||
}
|
||
}
|
||
if req.LikeType == constant.LikeTypeDrama {
|
||
vList = mediaser.FillMedias(infos, uid, true)
|
||
} else {
|
||
vList = mediaser.FillNotStatusMedias(infos, true)
|
||
}
|
||
|
||
//case constant.LikeTypeComment:
|
||
default:
|
||
return
|
||
}
|
||
|
||
//})
|
||
|
||
//common.Go(func() {
|
||
// defer wg.Done()
|
||
// total, _ = countLikeVidByUID(uid, constant.LikeTypeSP)
|
||
//})
|
||
//wg.Wait()
|
||
return vList, hasNext, nil
|
||
}
|
||
|
||
func countLikeVidByUID(uid uint64, t string) (total int64, err error) {
|
||
likes, err := likemod.FindLikeIDSByTypeAndUID(t, uid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
ids := make([]primitive.ObjectID, 0)
|
||
for _, v := range likes {
|
||
ids = append(ids, v.ObjID)
|
||
}
|
||
if len(ids) < 1 {
|
||
return
|
||
}
|
||
total, err = vidmod.CountPayVidsByUIDS(ids)
|
||
if err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
func countPay4VidLogByUID(uid uint64, newsType string) (total int64, err error) {
|
||
payvids, err := payvidlgmod.FindManyPay4VidLogByUID(uid, newsType)
|
||
if err != nil {
|
||
return
|
||
}
|
||
ids := make([]primitive.ObjectID, 0)
|
||
for _, v := range payvids {
|
||
ids = append(ids, v.VideoID)
|
||
}
|
||
if len(ids) < 1 {
|
||
return
|
||
}
|
||
total, err = vidmod.CountPayVidsByUIDS(ids)
|
||
if err != nil {
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// IVList () 作品
|
||
func IVList(uid uint64, sortType string, pageSize, pageNumber uint64, playTimeType int) (vList []*vidmod.VideoInfoResp, total int64, hasNext bool, err error) {
|
||
skip := (pageNumber - 1) * pageSize
|
||
limit := pageSize
|
||
vModels, total, hasNext, err := vidmod.GetMyWorkList(uid, sortType, skip, limit, playTimeType)
|
||
vList = vidhelpser.EncodeVideoInfoBlogger(uid, vModels)
|
||
return
|
||
}
|
||
|
||
// HisVList () 作品
|
||
func HisVList(uid uint64, oid uint64, pageSize, pageNumber uint64, sortType string, playTimeType int) (vList []*vidmod.VideoInfoResp, total int64, hasNext bool, err error) {
|
||
var (
|
||
// vModels []*vidmod.VideoModel
|
||
skip, limit int64
|
||
status = 1
|
||
)
|
||
skip = int64((pageNumber - 1) * pageSize)
|
||
limit = int64(pageSize)
|
||
//获取rediskey
|
||
redisKey := redisconst.UserCollectionKey(oid, pageNumber, pageSize, playTimeType, sortType)
|
||
var data struct {
|
||
Vmodel []*vidmod.VideoModel
|
||
Total int64
|
||
HasNext bool
|
||
}
|
||
//尝试从redis读取
|
||
if strP, err1 := appg.Redis.Get(redisKey); strP == nil { //不成功从数据库读取
|
||
data.Vmodel, data.Total, data.HasNext, err = vidmod.GetHisWorkList(oid, skip, limit, &status, sortType, playTimeType)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if err1 == nil {
|
||
go func() { //写入缓存
|
||
b, err := json.Marshal(data)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = appg.Redis.Set(redisKey, string(b), redisconst.UserCollectionExpire())
|
||
if err != nil {
|
||
fmt.Println(err.Error())
|
||
|
||
}
|
||
}()
|
||
}
|
||
} else {
|
||
if err = json.Unmarshal([]byte(*strP), &data); err != nil {
|
||
return
|
||
}
|
||
}
|
||
total = data.Total
|
||
hasNext = data.HasNext
|
||
vList = vidhelpser.NewEncodeVideoInfo(uid, data.Vmodel)
|
||
return
|
||
}
|
||
|
||
// FaqList 常见问题列表
|
||
func FaqList(cate string, s commod.StdQuery) (data faqmod.FaqListRes, err error) {
|
||
result, total, err := faqmod.FaqList(cate, true, s)
|
||
if err != nil {
|
||
log.Warn("[METHOD]==>FaqList error", log.E(err))
|
||
return
|
||
}
|
||
data.Total = total
|
||
data.List = result
|
||
return
|
||
}
|
||
|
||
type DLInfo struct {
|
||
AppID int `json:"appID"`
|
||
UID uint64 `json:"uid"`
|
||
}
|
||
|
||
func (this *DLInfo) Dec(data string) error {
|
||
b, err := base64.StdEncoding.DecodeString(data)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return json.Unmarshal(b, this)
|
||
}
|
||
|
||
func (this *DLInfo) Enc() (string, error) {
|
||
b, err := json.Marshal(*this)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
resStr := base64.StdEncoding.EncodeToString(b)
|
||
return resStr, nil
|
||
}
|
||
|
||
func checkAccountPassword(ctx context.Context, user *usermod.User, account, pasword string) (code stderr.Code) {
|
||
if account == "" || user.UID != 0 {
|
||
return stderr.Success
|
||
}
|
||
u, err := usermod.FindUserByAccount(account)
|
||
if err != nil {
|
||
log.ErrorX(ctx, "find user by account when register failed", log.Any("account", account), log.E(err))
|
||
return stderr.ErrDbQueryError
|
||
}
|
||
if u == nil {
|
||
code = stderr.ErrInvalidAccount
|
||
return
|
||
}
|
||
*user = *u
|
||
return stderr.Success
|
||
}
|
||
|
||
func checkLoginEmailMobilePassword(ctx context.Context, user *usermod.User, mobile, email, captchaCode, pwd string) (
|
||
code stderr.Code) {
|
||
if (mobile == "" && email == "") || user.UID != 0 {
|
||
return stderr.Success // if login without both email and mobile then try other ways, so dont't return err
|
||
}
|
||
if code = notiser.VerifyCaptchaCode(ctx, mobile, email, captchaCode); code != stderr.Success {
|
||
return
|
||
}
|
||
// if login with phone number, check whether the mobile has been registered and whether pasword matches
|
||
if mobile != "" {
|
||
u, err := usermod.FindUserByMobile(mobile)
|
||
if err != nil {
|
||
code = stderr.ErrDbQueryError
|
||
return
|
||
}
|
||
if u == nil {
|
||
code = stderr.ErrMobileInvalid
|
||
return
|
||
}
|
||
*user = *u
|
||
}
|
||
return
|
||
}
|
||
|
||
func H5LoginByQRCode(ctx context.Context, content, ip string) (userInfo usermod.UserRes, code stderr.Code) {
|
||
uid, err := GetUIDFromQrCnt(content)
|
||
if err != nil {
|
||
log.ErrorX(ctx, "get uid from qr code failed", log.Any("content", content), log.E(err))
|
||
code = stderr.ErrQrInvalid
|
||
return
|
||
}
|
||
u, err := usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
log.ErrorX(ctx, "find user by uid failed", log.Any("uid", uid), log.E(err))
|
||
code = stderr.UserIsNotExists
|
||
return
|
||
}
|
||
if u == nil || u.UID == 0 {
|
||
log.WarnX(ctx, "user invalid")
|
||
code = stderr.UserIsNotExists
|
||
return
|
||
}
|
||
//切换账号 刷新token
|
||
token, err := authuser.GenToken(&authuser.Claims{UID: u.UID, Timestamp: time.Now().UnixNano(), Type: uint8(u.Type)})
|
||
if err != nil {
|
||
code = stderr.ErrInterServerError
|
||
return
|
||
}
|
||
if err = lastLogin(uid, token, constant.SysTypeIOS); err != nil {
|
||
return
|
||
}
|
||
authuser.RevokeTokenCache(uid)
|
||
if userInfo, err = Info(uid); err != nil {
|
||
return
|
||
}
|
||
userInfo.Token = token
|
||
code = stderr.Success
|
||
|
||
return
|
||
}
|
||
|
||
// H5Login h5登陆
|
||
func H5Login(ctx context.Context, ip string, p usermod.LoginWithH5, ua ua.UA) (userInfo usermod.UserRes, code stderr.Code, err error) {
|
||
code = stderr.Success
|
||
var u = &usermod.User{}
|
||
var uid uint64
|
||
var token string
|
||
var isRegister bool
|
||
// check and find user by email of mobile if provided
|
||
if code = checkLoginEmailMobilePassword(ctx, u, p.Mobile, p.Email, p.Code, p.PassWord); code != stderr.Success {
|
||
return
|
||
}
|
||
// check and find user by account if provided
|
||
if code = checkAccountPassword(ctx, u, p.Account, p.PassWord); code != stderr.Success {
|
||
return
|
||
}
|
||
// if login without email and mobile, then check device id
|
||
if u.UID == 0 {
|
||
user, err := usermod.FindUserByDevID(p.DevID)
|
||
if err != nil {
|
||
code = stderr.ErrServerUnavailable
|
||
return userInfo, code, err
|
||
}
|
||
if user != nil {
|
||
u = user
|
||
}
|
||
}
|
||
// if it's a new user, then check info in clipboard
|
||
if u.UID == 0 {
|
||
// 单 IP 注册总量闸:先于建号判定。24h 滚动窗口内同一真实 IP 注册数超额直接拒绝并拉黑,
|
||
// 堵住"卡着 IPAutoBlock 的 5 分钟速率闸上限、跨窗口累计出成百上千个号"——速率闸过期即清零,管不住总量。
|
||
if code = checkRegisterQuota(ip); code != stderr.Success {
|
||
return
|
||
}
|
||
dc, pc, puc, tid := parseCutInfo(p.CutInfos, ip, p.SysType)
|
||
log.Info("DeviceLogin Register info", log.Any("devLogin", p), log.Any("ip", ip))
|
||
devType := "h5"
|
||
if p.IsAppStore {
|
||
devType = "ios"
|
||
}
|
||
uid, token, err = Register(dc, pc, puc, p.DevID, ip, p.SysType, "", devType, p.Mobile, "", ua.Terminal, ua, tid)
|
||
if err != nil {
|
||
return
|
||
}
|
||
isRegister = true
|
||
} else { // user already existed, then refersh its token
|
||
uid = u.UID
|
||
//切换账号 刷新token
|
||
token, _ = authuser.GenToken(&authuser.Claims{UID: u.UID, Timestamp: time.Now().UnixNano(), Type: uint8(u.Type)})
|
||
if err = lastLogin(uid, token, p.SysType); err != nil {
|
||
return
|
||
}
|
||
authuser.RevokeTokenCache(uid)
|
||
}
|
||
if userInfo, err = Info(uid); err != nil {
|
||
return
|
||
}
|
||
userInfo.Token = token
|
||
userInfo.IsRegister = isRegister
|
||
|
||
return
|
||
}
|
||
|
||
// registerQuota* 为"单 IP 注册总量闸"参数:registerQuotaWindow 滚动窗口内,同一真实 IP 最多允许
|
||
// registerQuotaLimit 次注册。区别于 IPAutoBlock 的"每 5 分钟速率闸"——后者窗口过期即清零,拦不住
|
||
// 卡着速率上限、跨窗口长期累计的刷号;总量闸按较长窗口封顶,直接堵累计。上限偏严,大出口 NAT 若误伤再调。
|
||
const (
|
||
registerQuotaWindow = 24 * time.Hour
|
||
registerQuotaLimit = 10
|
||
)
|
||
|
||
// incrRegisterQuotaScript 原子地对总量闸计数 +1,并在计数首次达到 1 时设置窗口 TTL,返回自增后的计数。
|
||
// 用 Lua 保证 INCR+EXPIRE 原子(避免"加了没设过期"的永久残留);Eval 会回传 Redis 错误,供调用方 fail-closed。
|
||
const incrRegisterQuotaScript = `local c = redis.call('INCR', KEYS[1])
|
||
if c == 1 then
|
||
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
||
end
|
||
return c`
|
||
|
||
// checkRegisterQuota 单 IP 注册"总量闸":registerQuotaWindow(24h)内同一真实 IP 注册数超过 registerQuotaLimit
|
||
// 即拒绝并拉黑。与 IPAutoBlock(5 分钟速率闸)互补,专堵"几分钟内爆发 / 卡节奏长期累计"这类速率闸拦不住的刷号。
|
||
// Redis 异常时 fail-closed(拒绝):注册是低频操作,宁可此刻挡下新注册,也不在 Redis 被打抖时放行刷号——
|
||
// appg.Redis.Incr 封装出错会静默返回 0 相当于放行,这里改用 Eval 显式拿 error 兜住。
|
||
func checkRegisterQuota(ip string) stderr.Code {
|
||
if !appg.ShouldEnforceIPRateLimit() {
|
||
return stderr.Success
|
||
}
|
||
if ip == "" || appg.Redis == nil {
|
||
return stderr.Success
|
||
}
|
||
key := fmt.Sprintf("register:quota:%s", ip)
|
||
res, err := appg.Redis.Eval(incrRegisterQuotaScript, []string{key}, int(registerQuotaWindow/time.Second))
|
||
if err != nil {
|
||
log.Warn("register quota eval failed, fail-closed reject", log.Any("ip", ip), log.E(err))
|
||
return stderr.ErrLoginTooFrequently
|
||
}
|
||
cnt, _ := res.(int64)
|
||
if cnt > registerQuotaLimit {
|
||
blockRegisterIP(ip, "H5注册24h总量超限自动拉黑")
|
||
return stderr.ErrLoginTooFrequently
|
||
}
|
||
return stderr.Success
|
||
}
|
||
|
||
// checkRegisterFrequencyAndBlock 注册"速率闸":1 分钟内注册超过 10 次自动拉黑 IP,拦瞬时高频爆发。
|
||
func checkRegisterFrequencyAndBlock(ip string) {
|
||
if !appg.ShouldEnforceIPRateLimit() {
|
||
return
|
||
}
|
||
if ip == "" {
|
||
return
|
||
}
|
||
key := fmt.Sprintf("register:freq:%s", ip)
|
||
cnt := appg.Redis.Incr(key)
|
||
if cnt == 1 {
|
||
_, _ = appg.Redis.ExpireKey(key, 1*time.Minute)
|
||
}
|
||
if cnt > 10 {
|
||
blockRegisterIP(ip, "H5注册频率过高自动拉黑")
|
||
}
|
||
}
|
||
|
||
// blockRegisterIP 将 ip 加入注册黑名单:落库 + 刷 Redis 黑名单集合,使其后续请求被 ipblock.IPBlock 中间件
|
||
// 在入口直接拦截(连 handler 都进不来)。供总量闸/频率闸命中时复用。
|
||
func blockRegisterIP(ip, remark string) {
|
||
if ip == "" || appg.Redis == nil {
|
||
return
|
||
}
|
||
if err := ipblockmod.Insert(&ipblockmod.IPBlock{
|
||
IP: ip,
|
||
Type: ipblockmod.BlockAct(constant.Register),
|
||
Remark: remark,
|
||
}); err != nil {
|
||
log.Error("insert register ip block failed", log.Any("ip", ip), log.E(err))
|
||
}
|
||
// 刷新IP黑名单缓存
|
||
blockKey := redisconst.IPBlockKey(constant.Register)
|
||
_, _ = appg.Redis.SAdd(blockKey, []interface{}{ip})
|
||
log.Info("auto block register ip", log.Any("ip", ip), log.Any("remark", remark))
|
||
}
|
||
|
||
// ResetPassword 手机重置密码
|
||
func ResetPassword(ctx context.Context, encrypted, password string) stderr.Code {
|
||
c, err := base64.StdEncoding.DecodeString(encrypted)
|
||
if err != nil {
|
||
return stderr.Failure
|
||
}
|
||
baseBytes, err := crypt.AesDecrypt(string(c), appg.Conf.Kefu.AppSecret)
|
||
if err != nil {
|
||
return stderr.Failure
|
||
}
|
||
req := usermod.ResetPasswordRequest{}
|
||
if err = json.Unmarshal([]byte(baseBytes), &req); err != nil || req.Mobile == "" {
|
||
return stderr.Failure
|
||
}
|
||
return resetPasswordByMobile(ctx, req, password)
|
||
}
|
||
|
||
func resetPasswordByMobile(ctx context.Context, req usermod.ResetPasswordRequest, password string) stderr.Code {
|
||
//手机号是否绑定
|
||
user, err := usermod.FindUserByMobile(req.Mobile)
|
||
if err != nil {
|
||
return stderr.Failure
|
||
}
|
||
if user == nil || (user.UID != req.UID) {
|
||
return stderr.UserIsNotExists
|
||
}
|
||
u, err := usermod.Update(user.UID, usermod.UserSelector{PassWord: &password})
|
||
if err != nil || u == nil {
|
||
log.ErrorX(ctx, "reset pwd by mobile failed", log.Any("req", req), log.Any("pwd", password), log.E(err))
|
||
return stderr.ErrNetWorkBusy
|
||
}
|
||
return stderr.Success
|
||
}
|
||
|
||
// GenResetWorld 生成验证结果字符串
|
||
func GenResetWorld(ctx context.Context, mobile, email string) (stderr.Code, string) {
|
||
var user *usermod.User
|
||
if mobile != "" {
|
||
//手机号是否绑定
|
||
user, err := usermod.FindUserByMobile(mobile)
|
||
if err != nil {
|
||
return stderr.Failure, ""
|
||
}
|
||
if user == nil {
|
||
return stderr.UserIsNotExists, ""
|
||
}
|
||
}
|
||
mrp := usermod.ResetPasswordRequest{
|
||
Mobile: mobile,
|
||
Email: email,
|
||
UID: user.UID,
|
||
TimeStamp: time.Now().Unix(),
|
||
RandNum: rand.Int63n(9999),
|
||
}
|
||
baseBytes, _ := json.Marshal(mrp)
|
||
sign, err := crypt.AesEncrypt(string(baseBytes), appg.Conf.Kefu.AppSecret)
|
||
if err != nil {
|
||
return stderr.Failure, ""
|
||
}
|
||
return stderr.Success, base64.StdEncoding.EncodeToString(sign)
|
||
}
|
||
|
||
// MobileRegister 手机注册
|
||
func MobileRegister(uid uint64, mobile, passWord, sys string) (*usermod.UserRes, stderr.Code) {
|
||
//手机号是否绑定
|
||
user, err := usermod.FindUserByMobile(mobile)
|
||
if err != nil {
|
||
return nil, stderr.Failure
|
||
}
|
||
if user != nil {
|
||
if user.UID == uid {
|
||
return nil, stderr.ErrMobileHasBind
|
||
}
|
||
return nil, stderr.ErrMobileHasBindByOther
|
||
}
|
||
user, err = usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return nil, stderr.Failure
|
||
}
|
||
if user.MobileBindAt != nil {
|
||
return nil, stderr.ErrInvalidRequest
|
||
}
|
||
if err = updateUserMobile(uid, mobile, passWord); err != nil {
|
||
log.Warn("updateUserMobile error", log.E(err))
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
return refreshToken(uid, sys, uint8(user.Type))
|
||
}
|
||
|
||
// RegisterH5 H5注册.手机号、邮箱、账号
|
||
func RegisterH5(ctx context.Context, uid uint64, sys string, req RegisterRequestH5) (*usermod.UserRes, stderr.Code) {
|
||
// if code := notiser.VerifyCaptchaCode(ctx, req.Mobile, req.Email, req.Code); code != stderr.Success {
|
||
// return nil, code
|
||
// }
|
||
// if req.Mobile = strings.TrimSpace(req.Mobile); req.Mobile != "" {
|
||
// return MobileRegister(uid, req.Mobile, "", sys)
|
||
// }
|
||
if req.Account = strings.TrimSpace(req.Account); req.Account != "" {
|
||
return accountRegister(ctx, uid, req.Account, "", sys)
|
||
}
|
||
return nil, stderr.ErrInvalidRequest
|
||
}
|
||
|
||
func accountRegister(ctx context.Context, uid uint64, account, password, sys string) (*usermod.UserRes, stderr.Code) {
|
||
// if n, err := filterser.CheckIllegalWords(account); n > 0 || err != nil {
|
||
// if err != nil {
|
||
// log.ErrorX(ctx, "check illegal account failed", log.Any("account", account), log.E(err))
|
||
// }
|
||
// return nil, stderr.ErrAccountHasIllegalContent
|
||
// }
|
||
user, err := usermod.FindUserByAccount(account)
|
||
if err != nil {
|
||
return nil, stderr.Failure
|
||
}
|
||
if user != nil {
|
||
if user.UID == uid {
|
||
return nil, stderr.ErrAccountHasBinded
|
||
}
|
||
return nil, stderr.ErrAccountBindedByOther
|
||
}
|
||
user, err = usermod.FindUserByUID(uid)
|
||
if err != nil {
|
||
return nil, stderr.Failure
|
||
}
|
||
if user.AccountBindAt != nil {
|
||
return nil, stderr.ErrAccountHasBinded
|
||
}
|
||
newUid, err := idmod.GetNextUID() //获取新UID
|
||
if err != nil {
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
if err = updateUserAccount(uid, newUid, account, password); err != nil {
|
||
log.ErrorX(ctx, "accountRegister failed", log.Any("uid", uid), log.Any("account", account),
|
||
log.Any("passWord", password), log.E(err))
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
return refreshToken(newUid, sys, uint8(user.Type))
|
||
}
|
||
|
||
func refreshToken(uid uint64, sys string, userType uint8) (*usermod.UserRes, stderr.Code) {
|
||
//切换账号 刷新token
|
||
token, _ := authuser.GenToken(&authuser.Claims{UID: uid, Timestamp: time.Now().UnixNano(), Type: uint8(userType)})
|
||
if err := lastLogin(uid, token, sys); err != nil {
|
||
return nil, stderr.ErrNetWorkBusy
|
||
}
|
||
common.Go(func() {
|
||
authuser.RevokeTokenCache(uid)
|
||
})
|
||
u, err := Info(uid)
|
||
if err != nil {
|
||
return nil, stderr.Success
|
||
}
|
||
u.Token = token
|
||
return &u, stderr.Success
|
||
}
|
||
|
||
// HandleUserAwardsExpire 处理用户奖章是否过期
|
||
func HandleUserAwardsExpire(u *usermod.User) (awardsExpire []usermod.AwardsExpireResp) {
|
||
var numberIds []int
|
||
for _, i := range u.AwardsExpire {
|
||
awardExpire := usermod.AwardsExpireResp{
|
||
Number: i.Number,
|
||
IsExpire: false,
|
||
}
|
||
if i.AwardExpire.After(time.Now()) {
|
||
awardExpire.IsExpire = true
|
||
}
|
||
if !sli.ContainsInt(numberIds, i.Number) {
|
||
numberIds = append(numberIds, i.Number)
|
||
awardsExpire = append(awardsExpire, awardExpire)
|
||
continue
|
||
}
|
||
if len(awardsExpire) > 0 {
|
||
for k, v := range awardsExpire {
|
||
if v.Number == i.Number && awardExpire.IsExpire {
|
||
awardsExpire[k].IsExpire = awardExpire.IsExpire
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return awardsExpire
|
||
}
|
||
|
||
type (
|
||
PublishWorkReq struct {
|
||
commod.Page
|
||
Uid *uint64 `form:"uid" binding:"-"` // (获取他人的)用户ID,存在即获取这个用户的作品
|
||
NewsType *string `form:"newsType" binding:"-"` // (获取他人的)帖子类型
|
||
Status *int `form:"status" binding:"omitempty,oneof=0 1 2"` // (获取自己的)状态: 0:待审核 1:已发布 2:未通过
|
||
SortType *int `form:"sortType" binding:"omitempty,oneof=0 1 2"` // 1-最新 2-最多点赞
|
||
}
|
||
PublishWorkRep struct {
|
||
List []*vidmod.VideoInfoResp `json:"list"` // 视频列表
|
||
HasNext bool `json:"hasNext"` // 是否还有下一页
|
||
}
|
||
)
|
||
|
||
// GetUserPublishWorkList 获取用户发布的作品
|
||
func (req *PublishWorkReq) GetUserPublishWorkList(uid uint64) (rep PublishWorkRep, err error) {
|
||
var (
|
||
cond bson.M
|
||
sort bson.D
|
||
opt = options.Find().SetSkip(req.Skip64()).SetLimit(req.Limit64()).SetProjection(bson.M{"richText": 0})
|
||
)
|
||
if req.Uid != nil && *req.Uid > 0 {
|
||
// 查询他人
|
||
if req.NewsType == nil {
|
||
nt := vidmod.SP
|
||
req.NewsType = &nt
|
||
}
|
||
|
||
cond = bson.M{
|
||
"publisherID": *req.Uid,
|
||
"newsType": req.NewsType,
|
||
"status": vidmod.CheckPass,
|
||
}
|
||
sort = bson.D{{Key: "reviewAt", Value: -1}}
|
||
} else {
|
||
// 查询自己
|
||
if req.Status == nil {
|
||
status := vidmod.CheckPass
|
||
req.Status = &status
|
||
}
|
||
|
||
cond = bson.M{
|
||
"publisherID": uid,
|
||
"newsType": bson.M{"$in": []string{vidmod.SP, vidmod.SHORT, vidmod.PIC, vidmod.COVER}},
|
||
"status": req.Status,
|
||
}
|
||
sort = bson.D{{Key: "_id", Value: -1}}
|
||
}
|
||
if req.SortType != nil && *req.SortType == 1 {
|
||
sort = bson.D{{Key: "reviewAt", Value: -1}}
|
||
} else if req.SortType != nil && *req.SortType == 2 {
|
||
sort = bson.D{{Key: "likeCount", Value: -1}, {Key: "reviewAt", Value: -1}}
|
||
}
|
||
vModels, hasNext, err := vidmod.FindList(cond, opt.SetSort(sort))
|
||
|
||
vList := vidhelpser.EncodeVideoInfoBlogger(uid, vModels)
|
||
rep.List = vList
|
||
rep.HasNext = hasNext
|
||
|
||
return rep, err
|
||
}
|
||
|
||
type (
|
||
PublishNumReq struct {
|
||
}
|
||
PublishNumRep struct {
|
||
PassNUm int64 `json:"passNUm"` // 审核通过的数量
|
||
PendingReview int64 `json:"pendingReview"` // 待审核的数量
|
||
}
|
||
)
|
||
|
||
// GetUserPublishNum 获取用户发布的作品数量
|
||
func (req *PublishNumReq) GetUserPublishNum(uid uint64) (rep PublishNumRep, err error) {
|
||
cond := bson.M{
|
||
"publisherID": uid,
|
||
"newsType": bson.M{"$in": []string{vidmod.SP, vidmod.SHORT, vidmod.PIC, vidmod.COVER}},
|
||
"status": vidmod.WaitingCheck,
|
||
}
|
||
|
||
rep.PendingReview, err = vidmod.CountNum(cond)
|
||
if err != nil {
|
||
return
|
||
}
|
||
cond = bson.M{
|
||
"publisherID": uid,
|
||
"newsType": bson.M{"$in": []string{vidmod.SP, vidmod.SHORT, vidmod.PIC, vidmod.COVER}},
|
||
"status": vidmod.CheckPass,
|
||
}
|
||
|
||
rep.PassNUm, err = vidmod.CountNum(cond)
|
||
if err != nil {
|
||
return
|
||
}
|
||
return rep, err
|
||
}
|
||
|
||
type PublishWorkDeleteReq struct {
|
||
IDs []primitive.ObjectID `form:"ids"` //0:待审核 1:已发布 2:未通过
|
||
}
|
||
|
||
// UserPublishWorkDelete 删除我发布但未通过审核的作品[逻辑删除]
|
||
func (req *PublishWorkDeleteReq) UserPublishWorkDelete(uid uint64) (err error) {
|
||
cond := bson.M{
|
||
"_id": bson.M{"$in": req.IDs},
|
||
"publisherID": uid,
|
||
}
|
||
updater := bson.M{
|
||
"$set": bson.M{"status": vidmod.IsDeleted},
|
||
}
|
||
err = vidmod.UpdateMany(cond, updater)
|
||
return err
|
||
}
|