Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+319
View File
@@ -0,0 +1,319 @@
package usermod
import (
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"91porn-server/models/v/videocoupon"
)
const (
DevIDLogin = 0
TokenLogin = 1
MobileLogin = 2
QrCodeLogin = 3
CouponPositionUnUsed = "unused" // 未使用
CouponPositionUsed = "used" // 已使用
)
// UserSelector 用户修改参数
type UserSelector struct {
Gender *string `json:"gender,omitempty" bson:"gender,omitempty"` //性别
Name *string `json:"name,omitempty" bson:"name,omitempty"`
Portrait *string `json:"portrait,omitempty" bson:"portrait,omitempty"` //头像
Background *[]string `json:"background,omitempty" bson:"background,omitempty"` //背景图
Summary *string `json:"summary,omitempty" bson:"summary,omitempty"` //简介
HasPrivateZone *bool `json:"hasPrivateZone,omitempty" bson:"hasPrivateZone,omitempty"` // 已关闭私密圈权限
Region *string `json:"region,omitempty" bson:"region,omitempty"` //地区
Birthday *string `json:"birthday,omitempty" bson:"birthday,omitempty"` //生日
VipLevel *int `json:"vipLevel,omitempty" bson:"vipLevel,omitempty"` //vip等级
VipExpireDate *time.Time `json:"vipExpireDate,omitempty" bson:"vipExpireDate,omitempty"` //vip过期时间
CoinMouthExpireDate *time.Time `json:"coinMouthExpireDate" bson:"coinMouthExpireDate"` //金币月卡过期时间
HasLocked *bool `json:"hasLocked,omitempty" bson:"hasLocked,omitempty"` //已禁止登陆
HasBanned *bool `json:"hasBanned,omitempty" bson:"hasBanned,omitempty"` //已禁言
LockReason *string `json:"lockReason,omitempty" bson:"lockReason,omitempty"` //禁止登录的原因
LockImg *string `json:"lockImg,omitempty" bson:"lockImg,omitempty"` //禁止登录的原因截图
LockAt *time.Time `json:"lockAt,omitempty" bson:"lockAt,omitempty"` //封禁开始时间
ForbidUpload *bool `json:"forbidUpload,omitempty" bson:"forbidUpload,omitempty"` //是否禁止上传文件
Token *string `json:"token,omitempty" bson:"token,omitempty"` //用户令牌
WatchCount *uint64 `json:"watchCount,omitempty" bson:"watchCount,omitempty"` //观看次数
UpdatedAt *time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` //更新时间
LastVisitAt *time.Time `json:"lastVisitAt,omitempty" bson:"lastVisitAt,omitempty"` //最后登陆时间
AppLock *string `json:"appLock,omitempty" bson:"appLock,omitempty"` //设备锁
IsDAgent *bool `json:"isDAgent,omitempty" bson:"isDAgent,omitempty"` //是商圈代理
LastVer *string `json:"lastVer,omitempty" bson:"lastVer,omitempty"` //最后登陆版本
TrueScore *int64 `json:"trueScore,omitempty" bson:"trueScore,omitempty"` //用户真实性得分
IsDirect *bool `json:"isDirect,omitempty" bson:"isDirect,omitempty"` //是直推用户
DiscCode *string `json:"districtCode,omitempty" bson:"districtCode,omitempty"` //商区码
PromSeqe *string `json:"promSeqe,omitempty" bson:"promSeqe,omitempty"` //推广序列
SuperUser *bool `json:"superUser" bson:"superUser,omitempty"` //大v
OfficialCert *bool `json:"officialCert" bson:"officialCert,omitempty"` //是否官方认证
TaxLevel *int64 `json:"taxLevel" bson:"taxLevel,omitempty"` //扣税等级,默认03.7
ChatPrice *int64 `json:"chatPrice" bson:"chatPrice,omitempty"` //私信价格
LastSysType *string `json:"lastSysType,omitempty" bson:"lastSysType,omitempty"` //最后登陆系统类型
SnapVip *int `json:"snapVip,omitempty" bson:"snapVip,omitempty"` //临时vip
OriginVipExpire *time.Time `json:"originVipExpire,omitempty" bson:"originVipExpire,omitempty"` //临时vip记录的原始vip时间
OriginVipLevel *int `json:"originVipLevel,omitempty" bson:"originVipLevel,omitempty"` //临时vip记录的原始vip等级
AutoFollow *bool `json:"autoFollow" bson:"autoFollow,omitempty"` //是否配置被自动关注
HasEnteredGame *bool `json:"hasEnteredGame" bson:"hasEnteredGame,omitempty"` //是否进入过游戏
BankActName *string `json:"bankActName,omitempty" bson:"bankActName,omitempty"` //银行卡绑定名字
GoldVideoFreeExpire *time.Time `json:"goldVideoFreeExpire" bson:"goldVideoFreeExpire,omitempty"` //金币视频免费日期
GoldVideoFreeLimit *int64 `json:"goldVideoFreeLimit" bson:"goldVideoFreeLimit,omitempty"` // 金币视频免费限制门槛(包含acg)
WeeklyActiveValue *int64 `json:"weeklyActiveValue" bson:"weeklyActiveValue,omitempty"` //每周活跃度
OriginalSort *int `json:"originalSort" bson:"originalSort"` // 新版原创排序
VideoDeduction *float64 `json:"videoDeduction,omitempty" bson:"videoDeduction,omitempty"` //视频扣量,1:博主10单扣1单, 以此类推
VideoDeductionPayCount *int `json:"videoDeductionPayCount,omitempty" bson:"videoDeductionPayCount,omitempty"` //视频扣量购买次数
VideoDeductionCount *int `json:"videoDeductionCount,omitempty" bson:"videoDeductionCount,omitempty"` //视频扣量次数
MerchantAccount *string `json:"merchantAccount" bson:"merchantAccount,omitempty"` //商家账号
HasFirstChargeWaLi *bool `json:"hasFirstChargeWaLi,omitempty" bson:"hasFirstChargeWaLi,omitempty"` //是否首充充值瓦力
IsAdvertiser *bool `json:"isAdvertiser" bson:"isAdvertiser"` // 是否是打广告用户
HasWhoringCard *bool `json:"hasWhoringCard,omitempty" bson:"hasWhoringCard,omitempty"` // 是否有白嫖卡
PayVidDiscount *int `json:"payVidDiscount,omitempty" bson:"payVidDiscount,omitempty"` //支付视频折扣
GoldVideoCoupon *[]UserGoldVideoCoupon `json:"goldVideoCoupon,omitempty" bson:"goldVideoCoupon,omitempty"`
VideoFreeExpiration *time.Time `json:"videoFreeExpiration,omitempty" bson:"videoFreeExpiration,omitempty"` //视频免费过期时间
PassWord *string `json:"passWord,omitempty" bson:"passWord,omitempty"` //密码
VipName *string `json:"vipName,omitempty" bson:"vipName,omitempty"` //会员卡名称
LoufengBookDiscount *int `json:"loufengBookDiscount" bson:"loufengBookDiscount,omitempty"`
LoufengBookDiscountExpiration *time.Time `json:"loufengBookDiscountExpiration" bson:"loufengBookDiscountExpiration,omitempty"`
BannedTime *time.Time `json:"bannedTime" bson:"bannedTime,omitempty"` // 禁言时间
AllGoldVideoFree *bool `json:"allGoldVideoFree" bson:"allGoldVideoFree,omitempty"` // 是否所有金币视频免费
AiMateUid *string `json:"aiMateUid" bson:"aiMateUid,omitempty"` // ai伴侣uid
BroadcastExpire *time.Time `json:"broadcastExpire" bson:"broadcastExpire,omitempty"` //直播有效期
DramaExpire *time.Time `json:"dramaExpire" bson:"dramaExpire,omitempty"` //短剧权益有效期
}
// RenewDramaExpire 从当前有效期顺延短剧权益;已过期时从本次发放时间起算。
func RenewDramaExpire(current, now time.Time, days int) time.Time {
if current.After(now) {
return current.AddDate(0, 0, days)
}
return now.AddDate(0, 0, days)
}
func (u UserSelector) IsEmpty() bool {
return reflect.DeepEqual(u, UserSelector{})
}
// UserSelector 用户修改参数
type UserModifyReq struct {
Gender *string `json:"gender,omitempty" bson:"gender,omitempty"` //性别
Name *string `json:"name,omitempty" bson:"name,omitempty"`
Portrait *string `json:"portrait,omitempty" bson:"portrait,omitempty"` //头像
Background *[]string `json:"background,omitempty" bson:"background,omitempty"` //背景图
Summary *string `json:"summary,omitempty" bson:"summary,omitempty"` //简介
Region *string `json:"region,omitempty" bson:"region,omitempty"` //地区
Birthday *string `json:"birthday,omitempty" bson:"birthday,omitempty"` //生日
AppLock *string `json:"appLock,omitempty" bson:"appLock,omitempty"` //设备锁
UpdatedAt *time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"`
}
type UserOpt struct {
//显示全民代理
ShowProxy bool `json:"showProxy"`
}
// RechargeLevel 充值等级
type RechargeLevel struct {
//等级
Level int `json:"level"`
//当前位置
Current int `json:"current"`
//当前等级最大值
Limit int `json:"limit"`
}
// AwardsExpireResp 奖章返回数据
type AwardsExpireResp struct {
Number int `json:"number" bson:"number,omitempty"` // 奖章价值
IsExpire bool `json:"isExpire" bson:"isExpire,omitempty"` // 奖章是否过期 true有效 false无效
}
type UserRes struct {
//年龄
Age int `form:"age" json:"age" bson:"age"`
//作品总数
CollectionCount int64 `form:"collectionCount" json:"collectionCount"`
//是否是vip
IsVIP bool `json:"isVip" bson:"isVip"`
//推广落地页
PromoteURL string `json:"promoteURL" bson:"promoteURL"`
// 邀请者推广码
InviterCode string `json:"inviterCode" bson:"inviterCode"`
//用户当前定位城市
City string `json:"city"`
//用户购买视频总数
BuyVidCount int64 `json:"buyVidCount"`
//用户访问web携带的Token
WebToken string `json:"webToken"`
//用户登陆方式
LoginType int `json:"loginType"`
// 邀请者推广码
AppStoreCode string `json:"appStoreCode" bson:"appStoreCode"`
// 视频折扣卡到期时间
VideoDiscountExpiration time.Time `json:"videoDiscountExpiration"`
// 新版金币视频观影券
NewGoldVideoCoupon []GoldVideoCoupon `json:"newGoldVideoCoupon"`
//我的图片作品总数
HappinessPlazaCount int64 `form:"happinessPlazaCount" json:"happinessPlazaCount"`
TotalInvite int64 `form:"totalInvite" json:"totalInvite"`
IsFollow bool `json:"isFollow"`
//是否弹领取框
IsPopUps bool `form:"isPopUps" json:"isPopUps"` //是否弹出领取框
WhoringCard WhoringCardInfo `json:"whoringCard" bson:"whoringCard"` // 白嫖卡信息
AdverAbTestShowType int `json:"adverAbTestShowType"` // 广告ab测展示类型 -1-首次免广告 0-所有广告都可以观看 其他数字N-新人注册N分钟内免广告
ImEnabled bool `json:"imEnabled"` // IM 功能是否启用
ImToken string `json:"imToken"` // IM 客户端登录 token
ImUserID int64 `json:"imUserId"` // IM 平台用户 ID
ImDynamicDomain string `json:"dynamicConfigDomain"` // IM SDK 动态配置域名
ImSocketURL string `json:"socketURL"` // IM SDK WebSocket 地址
User
UserOpt
RechargeLevel
IsUpgrade bool `json:"isUpgrade" bson:"isUpgrade"` // 是否vip升级
}
type WhoringCardInfo struct {
HasWhoringCard bool `json:"hasWhoringCard" bson:"hasWhoringCard"` // 是否有白嫖卡
DetailInfo []DetailInfo `json:"detailInfo" bson:"detailInfo"` // 详细信息
}
type DetailInfo struct {
ID primitive.ObjectID `json:"id" bson:"id"` // 白嫖卡ID
HasReturn bool `json:"hasReturn" bson:"hasReturn"` // 是否返现白嫖卡
TotalDays int64 `json:"totalDays" bson:"totalDays"` // 需要打卡天数
CurrentSignDays int64 `json:"currentSignDays" bson:"currentSignDays"` // 连续打卡天数
ForgetSignDays int64 `json:"forgetSignDays" bson:"forgetSignDays"` // 忘记打卡天数
RenewalSignDays int64 `json:"renewalSignDays" bson:"renewalSignDays"` // 补签打卡天数
RenewalSignPrice int64 `json:"renewalSignPrice" bson:"renewalSignPrice"` // 补签打卡价格
StartTime time.Time `json:"startTime" bson:"startTime"` // 开始时间
EndTime time.Time `json:"endTime" bson:"endTime"` // 结束时间
RecordTime []Record `json:"recordTime" bson:"recordTime"` // 打卡时间
}
type Record struct {
SignTime string `json:"signTime" bson:"signTime"` // 打卡时间
IsSign bool `json:"isSign" bson:"isSign"` // 是否打卡
}
// DevLoginP 块设备登陆参数
type DevLoginP struct {
DevID string `json:"devID"`
DevType string `json:"devType" ` //设备型号
SysType string `json:"sysType"` //操作系统类型 安卓 IOS
Ver string `json:"ver" ` //APP版本号
DevToken string `json:"devToken" bson:"devToken"` //设备token ios 推送用
CutInfos string `json:"cutInfos"` //剪切板信息
Mobile string `json:"mobile"` //手机号
QrCnt string `json:"qrCnt"` //二维码内容
}
// BaseInfo 返回的用户信息
type BaseInfo struct {
UID uint64 `json:"uid" bson:"uid"`
Name string `json:"name" bson:"name"`
Gender string `json:"gender" bson:"gender"`
Portrait string `json:"portrait" bson:"portrait"`
HasLocked bool `json:"hasLocked" bson:"hasLocked"` //已禁止登陆
HasBanned bool `json:"hasBanned" bson:"hasBanned"` //已禁言
}
// BaseInfoVip 返回的用户信息
type BaseInfoVip struct {
//用户id
UID uint64 `json:"uid"`
//姓名
Name string `json:"name"`
//性别
Gender string `json:"gender"`
//头像
Portrait string `json:"portrait"`
//已禁止登陆
HasLocked bool `json:"hasLocked"`
//已禁言
HasBanned bool `json:"hasBanned"`
//vip登记
VipLevel int `json:"vipLevel"`
//是否是vip
IsVip bool `json:"isVip"`
//充值等级
RechargeLevel int `json:"rechargeLevel" ` //头像显示的vip等级,累计充值决定
//是否是大v
SuperUser bool `json:"superUser"` //大v
//活跃值
ActiveValue int `json:"activeValue"` //活跃度
//是否官方认证
OfficialCert bool `json:"officialCert"` //是否官方认证
//年龄
Age int `json:"age"`
//关注数
Follows int64 `json:"follows"`
//粉丝数
Fans int64 `json:"fans"`
TotalWorks int64 `json:"totalWorks" bson:"totalWorks"` // 总作品数
Summary *string `json:"summary,omitempty" bson:"summary,omitempty"` // 简介
Awards []int `json:"awards"` // 用户奖章
UpTag string `json:"upTag" bson:"upTag"` // 用户标签
VipName string `json:"vipName" bson:"vipName"` // 用户VIP名称
VipExpireDate time.Time `json:"vipExpireDate" bson:"vipExpireDate"` // VIP过期时间
}
// UserInfo 用户基本信息和状态信息
type UserInfo struct {
BaseInfo
}
// LoginWithH5 h5登陆参数
type LoginWithH5 struct {
Mobile string `json:"mobile" ` // 手机号
Email string `json:"email"` // email
Account string `json:"account"` // 账号
Code string `json:"code" ` // 短信码
PassWord string `json:"passWord"` // 密码
DevID string `json:"devID" binding:"required"` // 设备id
SysType string `json:"sysType" binding:"required"` // 操作系统类型 安卓 IOS
CutInfos string `json:"cutInfos"` // 剪切板信息
IsAppStore bool `json:"isAppStore"` // 是否商店包用户
}
// LoginH5ByQRCode H5用户通过二维码登录
type LoginH5ByQRCode struct {
Content string `json:"content" binding:"required"` // 二维码内容
}
// GoldVideoCouponDetail 新版观影券详情
type GoldVideoCouponDetail struct {
Num int `json:"num"` // 观影券面值
Count int `json:"count"` // 观影券数量
Source videocoupon.GoldVideoCouponSource `json:"source"` // 观影券获得方式
}
// GoldVideoCoupon 观影券
type GoldVideoCoupon struct {
Position string `json:"position"` // 观影券位置,如“已获得”、“已使用”
Coupons []GoldVideoCouponDetail `json:"coupons"` // 观影券
}
type UserRights struct {
UID uint64 `json:"uid"` // 用户ID
GoldVideoCoupons []GoldVideoCoupon `json:"goldVideoCoupons"` // 观影券
Cards []Card `json:"cards"` // 各种会员卡特权卡信息
GoldVideoDiscount int `json:"videoDiscount"` // 视频折扣
VipExpireDate time.Time `json:"vipExpireDate"` // 会员到期时间
GoldVideoFreeExpire time.Time `json:"goldVideoFreeExpire"` // 金币视频免费到期时间
}
type Card struct {
Name string `json:"name"` // 卡片名称Name
Expiration time.Time `json:"expiration"` // 到期时间
}
type ResetPasswordRequest struct {
Email string `json:"email"` // email
Mobile string `json:"mobile"` // 电话号码
UID uint64 `json:"uid"` // 用户ID
TimeStamp int64 `json:"timestamp"` // 时间戳
RandNum int64 `json:"randNum"` // 随机数
}
+163
View File
@@ -0,0 +1,163 @@
package usermod
import (
"fmt"
"strings"
"time"
"91porn-server/common/log"
"91porn-server/common/pageopt"
"91porn-server/common/ysinterface/disc"
"91porn-server/models/commod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// DistrictStatKey
type DistrictStatKey struct {
DiscSeqe `bson:",inline"` //商区码
SysType string `bson:"sysType"` //系统类型 iOS Android
}
func (d DistrictStatKey) GetDiscCode() string {
return d.DistrictCode
}
func (d DistrictStatKey) GetPromSeqe() string {
return d.PromSeqe
}
func (d DistrictStatKey) GetSysType() string {
return d.SysType
}
func (d DistrictStatKey) String() string {
if d.DiscSeqe.String() == "" {
return ""
}
return strings.ToUpper(strings.Join([]string{d.DiscSeqe.String(), d.SysType}, "-"))
}
// DistrictStater
type DistrictStater = disc.DistrictStater
var emptyString string
// DiscSeqeUserCountMap 时间内商区/序列注册数
func DiscSeqeUserCountMap(mats ...Matcher) (map[DistrictStater]int64, error) {
mats = append(mats, (&DistrictCodeGtMatch{&emptyString}).New())
filter := pageopt.MergeM(mats)
if len(filter) == 0 {
log.Warn("DiscSeqeUserCountMap not have filter")
return make(map[DistrictStater]int64), nil
}
opt := (&options.FindOptions{}).SetProjection(bson.M{
"districtCode": 1,
"promSeqe": 1,
"sysType": 1,
})
list := []DistrictStatKey{}
if err := coll(nil).Find(&list, filter, opt); err != nil {
log.Warn(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "DiscNewUserCountByTime", table, "Find", err))
return nil, err
}
m := make(map[DistrictStater]int64)
for _, v := range list {
if v.String() != "" {
m[v] += 1
}
}
return m, nil
}
// DiscSeqeNewUserCountByTime 时间内商区/序列注册数
func DiscSeqeNewUserCountByTime(start time.Time, end time.Time, mats ...Matcher) (map[DistrictStater]int64, error) {
mats = append(mats, (&commod.DiscBindAtGTEAndLTMatch{GTE: &start, LT: &end}).New())
return DiscSeqeUserCountMap(mats...)
}
// DiscSeqeNewUIDSMap 时间内商区注册用户
func DiscSeqeNewUIDSMap(mats ...Matcher) (map[DistrictStater][]uint64, error) {
filter := pageopt.MergeM(mats)
opt := (&options.FindOptions{}).SetProjection(bson.M{
"districtCode": 1,
"promSeqe": 1,
"sysType": 1,
"uid": 1,
})
list := []struct {
DistrictStatKey `bson:",inline"` //商区码
UID uint64 `bson:"uid"`
}{}
if err := coll(nil).Find(&list, filter, opt); err != nil {
return nil, err
}
m := make(map[DistrictStater][]uint64, len(list))
for _, v := range list {
if v.String() != "" {
m[v.DistrictStatKey] = append(m[v.DistrictStatKey], v.UID)
}
}
return m, nil
}
// DiscSeqeNewUIDSMapByTime 时间内商区/序列注册用户
func DiscSeqeNewUIDSMapByTime(start time.Time, end time.Time, mats ...Matcher) (map[DistrictStater][]uint64, error) {
mats = append(mats, (&commod.DiscBindAtGTEAndLTMatch{GTE: &start, LT: &end}).New())
return DiscSeqeNewUIDSMap(mats...)
}
// DiscNewUIDSMap 商区注册用户
func DiscNewUIDSMap(mats ...Matcher) (map[DistrictStater][]uint64, error) {
filter := pageopt.MergeM(mats)
opt := (&options.FindOptions{}).SetProjection(bson.M{
"districtCode": 1,
"uid": 1,
})
list := []struct {
DistrictCode string `bson:"districtCode"` //商区码
UID uint64 `bson:"uid"`
}{}
if err := coll(nil).Find(&list, filter, opt); err != nil {
return nil, err
}
m := make(map[DistrictStater][]uint64, len(list))
for _, v := range list {
key := DistrictStatKey{DiscSeqe: DiscSeqe{DistrictCode: v.DistrictCode}}
if key.String() != "" {
m[key] = append(m[key], v.UID)
}
}
return m, nil
}
// DiscNewUIDSMapByTime 时间内商区注册用户
func DiscNewUIDSMapByTime(start time.Time, end time.Time, mats ...Matcher) (map[DistrictStater][]uint64, error) {
mats = append(mats, (&commod.DiscBindAtGTEAndLTMatch{GTE: &start, LT: &end}).New())
return DiscNewUIDSMap(mats...)
}
// DiscNewCountMapByTime 时间内商区/序列注册数
func DiscNewCountMapByTime(start time.Time, end time.Time, mats ...Matcher) (map[DistrictStater]int64, error) {
mats = append(mats, (&commod.DiscBindAtGTEAndLTMatch{GTE: &start, LT: &end}).New())
filter := pageopt.MergeM(mats)
opt := (&options.FindOptions{}).SetProjection(bson.M{
"districtCode": 1,
"uid": 1,
})
list := []struct {
DistrictCode string `bson:"districtCode"` //商区码
}{}
if err := coll(nil).Find(&list, filter, opt); err != nil {
return nil, err
}
m := make(map[DistrictStater]int64, len(list))
for _, v := range list {
key := DistrictStatKey{DiscSeqe: DiscSeqe{DistrictCode: v.DistrictCode}}
if key.String() != "" {
m[key] += 1
}
}
return m, nil
}
+27
View File
@@ -0,0 +1,27 @@
package usermod
import (
"testing"
"time"
)
func TestRenewDramaExpire(t *testing.T) {
now := time.Date(2026, 8, 26, 12, 0, 0, 0, time.UTC)
tests := []struct {
name string
current time.Time
days int
want time.Time
}{
{name: "starts from now", days: 30, want: now.AddDate(0, 0, 30)},
{name: "extends active entitlement", current: now.AddDate(0, 0, 10), days: 30, want: now.AddDate(0, 0, 40)},
{name: "refunds active entitlement", current: now.AddDate(0, 0, 30), days: -7, want: now.AddDate(0, 0, 23)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RenewDramaExpire(tt.current, now, tt.days); !got.Equal(tt.want) {
t.Fatalf("RenewDramaExpire() = %s, want %s", got, tt.want)
}
})
}
}
+13
View File
@@ -0,0 +1,13 @@
package usermod
import "fmt"
// MobileHasBindError 手机已被绑定
type MobileHasBindError struct {
UID uint64
DuplicateMobile string
}
func (m MobileHasBindError) Error() string {
return fmt.Sprintf("UID:%d Don't use Duplicate Mobile:%s \n", m.UID, m.DuplicateMobile)
}
+63
View File
@@ -0,0 +1,63 @@
package usermod
import (
"fmt"
"91porn-server/common/elastic"
"91porn-server/common/log"
"91porn-server/models"
)
var es *elastic.Client
const ESTable = models.ESInfoUserTable
func InitESIndex() {
es = elastic.Init()
var setting = elastic.M{
"settings": elastic.M{
"number_of_shards": elastic.NumberOfShards,
"number_of_replicas": elastic.NumberOfReplicas,
"analysis": elastic.M{
"analyzer": elastic.M{
"ik": elastic.M{
"tokenizer": elastic.AnalyzerIkSmart,
},
},
},
},
"mappings": elastic.M{
"properties": elastic.M{
"name": elastic.M{
"type": "text",
"analyzer": elastic.AnalyzerIkSmart,
"search_analyzer": elastic.AnalyzerIkSmart,
},
},
},
}
if err := es.CreateIndices(ESTable, setting); err != nil {
panic(fmt.Sprintf("%s index indeices err ==>[%+v]", ESTable, err))
}
}
func Search(keywords string, from int64, size int64) (data []*ESUserSource, err error) {
query := elastic.M{
"query": elastic.M{
"match": elastic.M{
"name": keywords,
},
},
"from": from,
"size": size,
}
if err = es.Search(ESTable, &data, query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "Search", table, "CommonSearch", err),
log.Any("keywords", keywords),
log.Any("from", from),
log.Any("size", size),
)
return nil, err
}
return
}
+218
View File
@@ -0,0 +1,218 @@
package usermod
import (
"91porn-server/common/pageopt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Matcher = pageopt.Matcher
const districtCode = "districtCode"
// DistrictCodeGtMatch
type DistrictCodeGtMatch struct {
GT *string
}
func (s *DistrictCodeGtMatch) New() Matcher {
return pageopt.NewGTMatch(districtCode, s.GT)
}
// DistrictCodeMatch
type DistrictCodeMatch struct {
DistrictCode *string
}
func (s *DistrictCodeMatch) New() Matcher {
return pageopt.NewAssignMatch(districtCode, s.DistrictCode)
}
// DistrictCodeINMatch
type DistrictCodeINMatch struct {
DistrictCodeList []string
}
func (d *DistrictCodeINMatch) New() Matcher {
return pageopt.NewInMatch(districtCode, d.DistrictCodeList)
}
// PromSeqeMatch
type PromSeqeMatch struct {
Seqe *string
}
func (d *PromSeqeMatch) New() Matcher {
return pageopt.NewAssignMatch("promSeqe", d.Seqe)
}
// SysTypeMatch
type SysTypeMatch struct {
SysType *string
}
func (s *SysTypeMatch) New() Matcher {
return pageopt.NewAssignMatch("sysType", s.SysType)
}
// IsDirectMatch
type IsDirectMatch struct {
IsDirect *bool
}
func (b *IsDirectMatch) New() Matcher {
return pageopt.NewAssignMatch("isDirect", b.IsDirect)
}
// DevIDRegexMatch
type DevIDRegexMatch struct {
DevID *string
}
func (d *DevIDRegexMatch) New() Matcher {
return pageopt.NewRegexMatch("devID", d.DevID, "i")
}
// VipLevelMatch
type VipLevelMatch struct {
VipLevel *int
}
func (v *VipLevelMatch) New() Matcher {
return pageopt.NewAssignMatch("vipLevel", v.VipLevel)
}
// NameRegexMatch
type NameRegexMatch struct {
Name *string
}
func (n *NameRegexMatch) New() Matcher {
return pageopt.NewRegexMatch("name", n.Name, "i")
}
// MobileMatch
type MobileMatch struct {
Mobile *string
}
func (m *MobileMatch) New() Matcher {
return pageopt.NewAssignMatch("mobile", m.Mobile)
}
// PromotionCodeMatch
type PromotionCodeMatch struct {
PromotionCode *string
}
func (p *PromotionCodeMatch) New() Matcher {
return pageopt.NewAssignMatch("promotionCode", p.PromotionCode)
}
// HasPromotionCodeMatch
type HasPromotionCodeMatch struct {
IsExisted *bool
}
func (h *HasPromotionCodeMatch) New() Matcher {
return pageopt.NewExistsMatch("promotionCode", h.IsExisted)
}
// AutoFollowMatch
type AutoFollowMatch struct {
AutoFollow *bool
}
func (l *AutoFollowMatch) New() Matcher {
return pageopt.NewAssignMatch("autoFollow", l.AutoFollow)
}
// LockedMatch
type LockedMatch struct {
Locked *bool
}
func (l *LockedMatch) New() Matcher {
return pageopt.NewAssignMatch("hasLocked", l.Locked)
}
// BannedMatch
type BannedMatch struct {
Banned *bool
}
func (b *BannedMatch) New() Matcher {
return pageopt.NewAssignMatch("hasBanned", b.Banned)
}
// CanUploadMatch
type ForbidUploadMatch struct {
ForbidUpload *bool
}
func (c *ForbidUploadMatch) New() Matcher {
return pageopt.NewAssignMatch("forbidUpload", c.ForbidUpload)
}
// TrueScoreGTEMatch
type TrueScoreGTEMatch struct {
GTE *int64
}
func (b *TrueScoreGTEMatch) New() Matcher {
return pageopt.NewGTEMatch("trueScore", b.GTE)
}
// TrueScoreMatch
type TrueScoreMatch struct {
TrueScore *int64
}
func (b *TrueScoreMatch) New() Matcher {
return pageopt.NewAssignMatch("trueScore", b.TrueScore)
}
type RegisterIpMatch struct {
RegisterIp *string
}
func (b *RegisterIpMatch) New() Matcher {
return pageopt.NewAssignMatch("registerIP", b.RegisterIp)
}
// CreatedAtGTEAndLTMatch
type CreatedAtGTEAndLTMatch = pageopt.CreatedAtGTEAndLTMatch
// UIDInMatch
type UIDInMatch = pageopt.UIDInMatch
// UIDMatch
type UIDMatch = pageopt.UIDMatch
type Sort = bson.D
var Sort_CreatedAt_n1 = Sort{{Key: "createdAt", Value: -1}}
type UserSlice []User
func List(sort bson.D, skip, limit *int64, matchs ...Matcher) (UserSlice, error) {
opt := (&options.FindOptions{})
if len(sort) != 0 {
opt.SetSort(sort)
}
if skip != nil {
opt.SetSkip(*skip)
}
if limit != nil {
opt.SetLimit(*limit)
}
filter := pageopt.MergeM(matchs)
userList := UserSlice{}
return userList, coll(nil).Find(&userList, filter, opt)
}
func Count(matchs ...Matcher) (int64, error) {
filter := pageopt.MergeM(matchs)
return coll(nil).Count(filter)
}
@@ -0,0 +1,39 @@
package usermod
import (
"testing"
"time"
)
func TestGetPaymentStatusPopupNewUserIgnoresFreeWatchCount(t *testing.T) {
now := time.Now()
tests := []struct {
name string
watchCount uint64
want string
}{
{
name: "remaining free views stays new unpaid",
watchCount: 3,
want: UserPaymentStatusPopupNewUnpay,
},
{
name: "exhausted free views stays new unpaid",
watchCount: 0,
want: UserPaymentStatusPopupNewUnpay,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
user := &User{
CreatedAt: now.Add(-time.Hour),
WatchCount: tt.watchCount,
}
if got := user.GetPaymentStatusPopup(); got != tt.want {
t.Fatalf("GetPaymentStatusPopup() = %q, want %q", got, tt.want)
}
})
}
}
+396
View File
@@ -0,0 +1,396 @@
package usermod
import (
"encoding/json"
"time"
"91porn-server/common/db"
"91porn-server/common/timeutil"
"91porn-server/models/commod"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var mdb *db.MongoDB
const (
_ = iota
ThreeDayVIP_SnapVip //3天临时永久vip
)
type UserType uint8
const (
UserUnkown UserType = iota
UserLoufengAgent //楼风代理 1
UserTourists //游客 2
)
// 用户分层弹窗-付费状态分层
const (
UserPaymentStatusPopupUnkown = "unkown" // 未知
UserPaymentStatusPopupNewUnpay = "new_unpay" // 新用户
UserPaymentStatusPopupUnder7DayUnpay = "under_7_day_unpay" // 7天内未付费用户
UserPaymentStatusPopupUnder7DayUnpayNoCountdown = "under_7_day_unpay_no_countdown" // 7天内未付费用户无倒计时
UserPaymentStatusPopupOver7DayUnpay = "over_7_day_unpay" // 7天后未付费用户
UserPaymentStatusPopupOver7DayNeedUpgrade = "over_7_day_need_upgrade" // 已付费但未达最高等级,需升级
UserPaymentStatusPopupMaxVIPLevel = "max_vip_level" // 达到最大vip等级
UserPaymentStatusPopupUnregistered = "unregistered" // 未注册用户
UserPaymentStatusPopupNormal = "normal" // 正常用户
)
type DiscSeqe = commod.DiscSeqe
type DiscDoc = commod.DiscDoc
// User 用户信息
type User struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
//用户id
UID uint64 `json:"uid" bson:"uid"`
//设备id
DevID string `json:"devID" bson:"devID"`
//设备类型
DevType string `json:"devType" bson:"devType"`
//设备token ios 推送用
DevToken string `json:"devToken" bson:"devToken"`
//注册IP
RegisterIP string `json:"registerIP" bson:"registerIP"`
//手机号
Mobile string `json:"mobile" bson:"mobile"`
//性别
Gender string `json:"gender" bson:"gender"`
//姓名
Name string `json:"name" bson:"name"`
//头像
Portrait string `json:"portrait" bson:"portrait"`
//背景图
Background []string `json:"background,omitempty" bson:"background,omitempty"`
//简介
Summary string `json:"summary" bson:"summary"`
//地区
Region string `json:"region" bson:"region"`
//生日
Birthday string `json:"birthday" bson:"birthday"`
VipName string `json:"vipName" bson:"vipName"`
// vip等级 0-非vip会员 1-月卡用户 2-季卡用户 3-年卡用户 4-永久会员
VipLevel int `json:"vipLevel" bson:"vipLevel"`
//已禁止登陆
HasLocked bool `json:"hasLocked" bson:"hasLocked"`
//禁止登录的原因
LockReason string `json:"lockReason" bson:"lockReason"`
//封禁开始时间
LockAt time.Time `json:"lockAt" bson:"lockAt"`
//禁止登录的原因截图
LockImg *string `json:"lockImg,omitempty" bson:"lockImg,omitempty"`
//已禁言
HasBanned bool `json:"hasBanned" bson:"hasBanned"`
//是否禁止上传帖子
ForbidUpload bool `json:"forbidUpload" bson:"forbidUpload"`
//用户令牌
Token string `json:"token" bson:"token"`
//观看次数
WatchCount uint64 `json:"watchCount" bson:"watchCount"`
//应用锁
AppLock string `json:"appLock" bson:"appLock"`
//用户真实性得分
TrueScore int64 `json:"trueScore" bson:"trueScore"`
//注册时登陆版本
Ver string `json:"ver" bson:"ver"`
//最后登陆版本
LastVer string `json:"lastVer" bson:"lastVer"`
//默认入口已处理版本,仅供后端判断当前版本是否首次进入
DefaultEntryHandledVer string `json:"-" bson:"defaultEntryHandledVer,omitempty"`
//系统类型
SysType string `json:"sysType" bson:"sysType"`
//最后登陆时间
LastVisitAt time.Time `json:"lastVisitAt" bson:"lastVisitAt"`
//最后登陆系统类型
LastSysType string `json:"lastSysType" bson:"lastSysType"`
//vip过期时间
VipExpireDate time.Time `json:"vipExpireDate" bson:"vipExpireDate"`
//金币月卡过期时间
CoinMouthExpireDate time.Time `json:"coinMouthExpireDate" bson:"coinMouthExpireDate"` //金币月卡过期时间
//创建时间
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
//修改时间
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
//手机绑定时间,缺省参数 不能删除omitempty
MobileBindAt *time.Time `json:"mobileBindAt" bson:"mobileBindAt,omitempty"`
//手机解绑时间,缺省参数 不能删除omitempty
MobileUnBindAt *time.Time `json:"mobileUnBindAt" bson:"mobileUnBindAt,omitempty"`
//是商区代理
IsDAgent bool `json:"isDAgent" bson:"isDAgent"`
//推广码
PromCode string `json:"promotionCode" bson:"promotionCode"`
//商区
DiscDoc `bson:",inline"`
//大v
SuperUser bool `json:"superUser" bson:"superUser"` // 0不是大v 1大v
SuperUserExpire time.Time `json:"superUserExpire" bson:"superUserExpire"` // 大V 持续时间
MerchantUser int `json:"merchantUser" bson:"merchantUser"` // 商家认证,0:用户,1商家
MerchantUserExpire time.Time `json:"merchantUserExpire" bson:"merchantUserExpire"` // 商家认证持续时间
//活跃度
ActiveValue int `json:"activeValue" bson:"activeValue"`
//是否官方认证
OfficialCert bool `json:"officialCert" bson:"officialCert"`
//扣税等级,默认03.7
TaxLevel int64 `json:"taxLevel" bson:"taxLevel"`
//登陆天数
LoginDays int64 `json:"loginDays" bson:"loginDays"`
//获得打赏的金额
Rewarded decimal.Decimal `json:"rewarded" bson:"rewarded"`
//临时vip
SnapVip bool `json:"snapVip" bson:"snapVip"`
//临时vip记录的原始vip时间
OriginVipExpire *time.Time `json:"-" bson:"originVipExpire,omitempty"`
//临时vip记录的原始vip等级
OriginVipLevel *int `json:"-" bson:"originVipLevel,omitempty"`
// 是否有白嫖卡
HasWhoringCard bool `json:"hasWhoringCard" bson:"hasWhoringCard"`
//楼凤全免过期时间
LouFengQmExpiration *time.Time `json:"louFengQmExpiration,omitempty" bson:"louFengQmExpiration,omitempty"`
//是否配置被自动关注
AutoFollow bool `json:"autoFollow" bson:"autoFollow"`
//是否进入过游戏
HasEnteredGame bool `json:"hasEnteredGame" bson:"hasEnteredGame"`
//赠送楼凤解锁次数
LouFengUnlockTimes int `json:"louFengUnlockTimes" bson:"louFengUnlockTimes"`
//金币视频免费日期
GoldVideoFreeExpire time.Time `json:"goldVideoFreeExpire" bson:"goldVideoFreeExpire"`
//每周活跃度
WeeklyActiveValue int64 `json:"weeklyActiveValue" bson:"weeklyActiveValue"`
//银行卡绑定名字
BankActName string `json:"bankActName" bson:"bankActName"`
// 新版原创排序
OriginalSort int `json:"originalSort" bson:"originalSort"`
//应用中心下载信息
PUC string `json:"puc" bson:"puc"`
//视频扣量,1:博主10单扣1单, 以此类推
VideoDeduction float64 `json:"videoDeduction" bson:"videoDeduction"`
//视频扣量购买次数
VideoDeductionPayCount int `json:"videoDeductionPayCount" bson:"videoDeductionPayCount"`
//视频扣量次数
VideoDeductionCount int `json:"videoDeductionCount" bson:"videoDeductionCount"`
//图片被购买数量(作为作者) 此项目暂不对外暴露 因此设置`json:"-"`
CoverPayCount int `json:"-" bson:"coverPayCount"`
//是否首充充值瓦力
HasFirstChargeWaLi bool `json:"hasFirstChargeWaLi" bson:"hasFirstChargeWaLi"`
//是否充值棋牌
HasFirstChargeVID bool `json:"hasFirstChargeVID" bson:"hasFirstChargeVID"`
//商家账号
MerchantAccount string `json:"merchantAccount" bson:"merchantAccount"`
//支付视频折扣
PayVidDiscount int `json:"payVidDiscount" bson:"payVidDiscount"`
//金币视频免费券
GoldVideoCoupon []UserGoldVideoCoupon `json:"goldVideoCoupon,omitempty" bson:"goldVideoCoupon,omitempty"`
//视频免费过期时间--仅对金币数少于特定数目的视频有效
VideoFreeExpiration *time.Time `json:"videoFreeExpiration" bson:"videoFreeExpiration,omitempty"`
//密码
PassWord string `json:"passWord" bson:"passWord"`
//邮箱
Email string `json:"email" bson:"email"`
//通过邮箱验证时间
EmailCheckedAt *time.Time `json:"emailCheckedAt" bson:"emailCheckedAt,omitempty"`
//用户自己设定的账号 用于找回账号重设密码
Account string `json:"account" bson:"account"`
//账号绑定时间
AccountBindAt *time.Time `json:"accountBindAt" bson:"accountBindAt"`
//用户类型
Type UserType `json:"userType" bson:"userType"`
//天数
LoufengBookExpiration time.Time `json:"loufengBookDiscountExpiration" bson:"loufengBookDiscountExpiration"`
TotalWorks int64 `json:"totalWorks" bson:"totalWorks"` // 总作品数
VidUploadCount int64 `json:"vidUploadCount" bson:"vidUploadCount"` // 视频上传次数
CoverUploadCount int64 `json:"coverUploadCount" bson:"coverUploadCount"` // 图片帖子上传次数
AiUndressCount int64 `json:"aiUndressCount" bson:"aiUndressCount"` // ai脱衣剩余次数
Awards []int `json:"awards" bson:"awards,omitempty"` // 奖励
AwardsExpire []AwardsExpire `json:"awardsExpire" bson:"awardsExpire,omitempty"` // 奖章过期时间
Follows int64 `json:"follows" bson:"follows"` // 关注数
Fans int64 `json:"fans" bson:"fans"` // 粉丝数
UpTag string `json:"upTag" bson:"upTag"` // 博主认证
LikeCount int `json:"likeCount" bson:"likeCount"` // 点赞数
ChatPrice int64 `json:"chatPrice" bson:"chatPrice,omitempty"` // 私信价格
IsAdvertiser bool `json:"isAdvertiser" bson:"isAdvertiser"` // 是否是打广告用户
AllGoldVideoFree bool `json:"allGoldVideoFree" bson:"allGoldVideoFree"` // 是否所有金币视频免费
BannedTime time.Time `json:"bannedTime" bson:"bannedTime"` // 禁言时间
AiMateUid string `json:"aiMateUid" bson:"aiMateUid"` // ai伴侣uid,对应第三方服务
BroadcastExpire time.Time `json:"broadcastExpire" bson:"broadcastExpire,omitempty"` //直播有效期
DramaExpire time.Time `json:"dramaExpire" bson:"dramaExpire,omitempty"` //短剧权益有效期
GoldVideoFreeLimit int64 `json:"goldVideoFreeLimit" bson:"goldVideoFreeLimit"`
SendMsgPrice int64 `json:"sendMsgPrice"` // 发送私信价格
IsRegister bool `json:"isRegister"` // 是否注册
Tid string `json:"tid" bson:"tid"` // 粘贴板传过来的tidtraceId
//已开启私密圈
HasPrivateZone bool `json:"hasPrivateZone" bson:"hasPrivateZone"`
OriVer string `json:"oriVer" bson:"oriVer"` // 原始版本
AdGroup commod.AdGroup `json:"adGroup" bson:"adGroup"` // 广告组
}
// AwardsExpire 奖章过期时间
type AwardsExpire struct {
AwardID primitive.ObjectID `json:"awardID" bson:"awardID,omitempty"` // 奖章
Number int `json:"number" bson:"number,omitempty"` // 奖章价值
AwardExpire time.Time `json:"awardExpire" bson:"awardExpire,omitempty"` // 奖章过期时间
}
type UploadCountInc struct {
TotalWorks *int64 `bson:"totalWorks,omitempty"` //视频上传次数
VidUploadCount *int64 `bson:"vidUploadCount,omitempty"` //视频上传次数
CoverUploadCount *int64 `bson:"coverUploadCount,omitempty"` //图片帖子上传次数
}
// LoufengAgent 楼凤经纪人
type LoufengAgent struct {
//楼凤经纪人账号,由统计中心同步过来
Account string `json:"agentAccount" bson:"agentAccount"`
//是否可赔付
Payable bool `json:"payable" bson:"payable"`
//保证金
Deposit int `json:"deposit" bson:"deposit"`
//简介
Introduce string `json:"introduce" bson:"introduce"`
}
// 用户金币视频抵用券
type UserGoldVideoCoupon struct {
//金币
Gold int `json:"gold" bson:"gold"`
//券数量
Count int `json:"count" bson:"count"`
}
type ESUser struct {
ID primitive.ObjectID `json:"id"`
UID uint64 `json:"uid"` //用户id
Gender string `json:"gender"` //性别
Name string `json:"name"`
Summary string `json:"summary"` //简介
Region string `json:"region"` //地区
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
HasLocked bool `json:"hasLocked"` //已禁止登陆
HasBanned bool `json:"hasBanned"` //已禁言
}
type ESUserSource struct {
ID string `json:"_id"`
Source ESUser `json:"_source"`
}
// UserStat 用户信息统计
type UserStat struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
}
func (u *User) Age() int {
bir := u.Birthday
bir = bir + " 00:00:00"
btm, _ := time.ParseInLocation("2006-01-02 15:04:05", bir, time.Local)
if btm.IsZero() {
return 0
}
return timeutil.Age(btm, time.Now())
}
func Init() {
mdb = db.Init(table)
initIndex()
}
// IsNewUser reports whether the user registered less than 24 hours ago.
func (u *User) IsNewUser(now time.Time) bool {
return u != nil && !u.CreatedAt.IsZero() && now.Before(u.CreatedAt.Add(24*time.Hour))
}
func (u User) IsPaidVIP() bool {
return u.VipExpireDate.After(time.Now()) && (u.VipLevel >= 1)
}
func (u User) IsVIP(now time.Time) bool {
return u.VipExpireDate.After(now)
}
func (u *User) GetSuperUser(now time.Time) bool {
if u.SuperUserExpire.Before(now) {
return false
} else {
return u.SuperUser
}
}
// VisitValue
type VisitValue struct {
UID uint64 `json:"uid"`
SysType string `json:"sysType"`
}
// RegistValue
type RegistValue = VisitValue
func (v VisitValue) JsonString() string {
ct, _ := json.Marshal(v)
return string(ct)
}
func GetGoldVideoFreeLimit(u *User) int64 {
if u == nil {
return 0
}
// 已经过了金币免费,则只有0
if !u.GoldVideoFreeExpire.After(time.Now()) {
return 0
}
var goldVideoFreeLimit int64 = 50
if u == nil {
return goldVideoFreeLimit
}
// 按照用户自身的限制门槛来判断
if u.GoldVideoFreeLimit > 0 {
goldVideoFreeLimit = u.GoldVideoFreeLimit
}
return goldVideoFreeLimit
}
// GetPaymentStatusPopup 计算用户付费状态分层,用于下发不同的分层弹窗配置。
// 注:91porn 的 VipLevel 语义为 0-非会员 1-月卡 2-季卡 3-年卡 4-永久会员,故最高等级为 4。
func (u *User) GetPaymentStatusPopup() string {
if u == nil {
return UserPaymentStatusPopupUnregistered
}
now := time.Now()
// 新用户:注册时间在一天以内,且不是付费会员。
// 旧版分层只按注册时间和付费状态判断,免费观看次数由新版付费引导单独处理。
if u.IsNewUser(now) && !u.IsPaidVIP() {
return UserPaymentStatusPopupNewUnpay
}
// 7天内未付费用户
if u.CreatedAt.AddDate(0, 0, 7).After(now) && !u.IsPaidVIP() {
if u.CreatedAt.AddDate(0, 0, 2).After(now) && !u.IsPaidVIP() { // 两天内未付费用户使用带倒计时图片源
return UserPaymentStatusPopupUnder7DayUnpay
}
return UserPaymentStatusPopupUnder7DayUnpayNoCountdown
}
// 7天后仍未付费用户
if u.CreatedAt.AddDate(0, 0, 7).Before(now) && !u.IsPaidVIP() {
return UserPaymentStatusPopupOver7DayUnpay
}
if u.IsPaidVIP() {
// 已付费,但未达最高等级(永久会员),引导续费升级
if !u.VipExpireDate.IsZero() && u.VipLevel < 4 {
return UserPaymentStatusPopupOver7DayNeedUpgrade
}
// 已达最高等级(永久会员)
if !u.VipExpireDate.IsZero() && u.VipLevel == 4 {
return UserPaymentStatusPopupMaxVIPLevel
}
}
return UserPaymentStatusPopupNormal
}
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
package usermod
import "time"
// RenewVIPLevel returns the level that should remain after extending VIP.
// An active higher level must not be downgraded by a lower-level renewal.
func RenewVIPLevel(currentExpire, now time.Time, currentLevel, renewedLevel int) int {
if currentExpire.After(now) && currentLevel > renewedLevel {
return currentLevel
}
return renewedLevel
}
+47
View File
@@ -0,0 +1,47 @@
package usermod
import (
"testing"
"time"
)
func TestRenewVIPLevel(t *testing.T) {
now := time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC)
tests := []struct {
name string
currentExpire time.Time
currentLevel int
renewedLevel int
want int
}{
{
name: "preserves active higher level",
currentExpire: now.Add(24 * time.Hour),
currentLevel: 3,
renewedLevel: 1,
want: 3,
},
{
name: "upgrades active lower level",
currentExpire: now.Add(24 * time.Hour),
currentLevel: 1,
renewedLevel: 3,
want: 3,
},
{
name: "uses renewed level after expiration",
currentExpire: now.Add(-time.Second),
currentLevel: 3,
renewedLevel: 1,
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RenewVIPLevel(tt.currentExpire, now, tt.currentLevel, tt.renewedLevel); got != tt.want {
t.Fatalf("RenewVIPLevel() = %d, want %d", got, tt.want)
}
})
}
}
+278
View File
@@ -0,0 +1,278 @@
package usermod
import (
"time"
"91porn-server/common/log"
"91porn-server/models/l/payvidlgmod"
"91porn-server/models/v/prdcthsomod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
type UserListSelector struct {
UID *uint64 `form:"uid,omitempty" json:"uid,omitempty" bson:"uid,omitempty"` //用户id
DevID *string `form:"devID,omitempty" json:"devID,omitempty" bson:"devID,omitempty"` //设备id
DevType *string `form:"devType,omitempty" json:"devType,omitempty" bson:"devType,omitempty"` //设备类型
DevToken *string `form:"devToken,omitempty" json:"devToken,omitempty" bson:"devToken,omitempty"` //设备toke`n ios 推送用
RegisterIP *string `form:"registerIP,omitempty" json:"registerIP,omitempty" bson:"registerIP,omitempty"` //注册IP
Mobile *string `form:"mobile,omitempty" json:"mobile,omitempty" bson:"mobile,omitempty"` //手机号
Gender *string `form:"gender,omitempty" json:"gender,omitempty" bson:"gender,omitempty"` //性别
Channel *string `form:"channel,omitempty" json:"channel,omitempty" bson:"channel,omitempty"`
Name *string `form:"name,omitempty" json:"name,omitempty" bson:"name,omitempty"`
PromotionCode *string `form:"promotionCode,omitempty" json:"promotionCode,omitempty" bson:"promotionCode,omitempty"` //推广码
Portrait *string `form:"portrait,omitempty" json:"portrait,omitempty" bson:"portrait,omitempty"` //头像
Summary *string `form:"summary,omitempty" json:"summary,omitempty" bson:"summary,omitempty"` //简介
Region *string `form:"region,omitempty" json:"region,omitempty" bson:"region,omitempty"` //地区
Birthday *string `form:"birthday,omitempty" json:"birthday,omitempty" bson:"birthday,omitempty"` //生日
VipLevel *int `form:"vipLevel,omitempty" json:"vipLevel,omitempty" bson:"vipLevel,omitempty"` //vip等级
HasLocked *bool `form:"hasLocked,omitempty" json:"hasLocked,omitempty" bson:"hasLocked,omitempty"` //已禁止登陆
HasBanned *bool `form:"hasBanned,omitempty" json:"hasBanned,omitempty" bson:"hasBanned,omitempty"` //已禁言
Token *string `form:"token,omitempty" json:"token,omitempty" bson:"token,omitempty"` //用户令牌
WatchCount *uint64 `from:"watchCount,omitempty" json:"watchCount,omitempty" bson:"watchCount,omitempty"` //观看次数
BankActName *string `json:"bankActName,omitempty" bson:"bankActName,omitempty"` //银行卡绑定名字
UpTag *string `form:"upTag" json:"upTag,omitempty" bson:"upTag,omitempty"` // 博主认证
}
// ExportUser 导出的用户信息
type ExportUser struct {
UID uint64 `xlsx:"用户id"`
//DevID string `xlsx:"设备id"`
DevType string `xlsx:"设备类型"`
RegisterIP string `xlsx:"注册IP"`
Mobile string `xlsx:"手机号"`
//Gender string `xlsx:"性别"`
Channel string `xlsx:"渠道号"`
//Name string `xlsx:"用户名"`
PromotionCode string `xlsx:"推广码"`
//Summary string `xlsx:"简介"`
//Region string `xlsx:"地区"`
//Birthday string `xlsx:"生日"`
//VipLevel int `xlsx:"vip等级"`
VipExpireDate time.Time `xlsx:"vip过期时间"`
CreatedAt time.Time `xlsx:"用户创建时间"`
LastVisitAt time.Time `xlsx:"最后活跃时间"`
//MobileBindAt *time.Time `xlsx:"手机绑定时间"`
//HasLocked bool `xlsx:"已禁止登陆"`
//HasBanned bool `xlsx:"已禁言"`
WatchCount int `xlsx:"七日观看次数"` //七日观看次数,和数据库存的不同
//VisitCount int `xlsx:"24小时内访问数"`
//TotalVideoCnt int `xlsx:"视频总数"`
//UndealVideoCnt int `xlsx:"未审核视频数"`
}
// 用户VIP返回数据
type UserVipInfoRes struct {
VideoDiscount int `json:"videoDiscount"` //视频折扣率
VideoDiscountExpiration time.Time `json:"videoDiscountExpiration"` // 视频折扣卡到期时间
VipExpireDate time.Time `json:"vipExpireDate"` //vip过期时间
GoldVideoFreeExpire time.Time `json:"goldVideoFreeExpire"` //金币视频免费日期
PayVidDiscount int `json:"payVidDiscount"` //支付视频折扣
VideoFreeExpiration *time.Time `json:"videoFreeExpiration"` //视频免费过期时间
GoldVideoCoupon []UserGoldVideoCoupon `json:"goldVideoCoupon"`
}
func FindAgentByAccount(acc string) (user *User, err error) {
if acc == "" {
return
}
err = coll(nil).FindOne(&user, bson.M{"agentAccount": acc})
return
}
func FindAllPayableAgents() ([]string, error) {
var p []struct {
AgentAcc string `bson:"agentAccount"`
}
if err := coll(nil).Find(&p, bson.M{
"agentAccount": bson.M{"$gt": ""},
"deposit": bson.M{"$gt": 0},
"payable": true,
"userType": UserLoufengAgent,
}, options.Find().SetProjection(bson.M{"agentAccount": 1})); err != nil {
return []string{}, err
}
agents := make([]string, len(p))
for i, agent := range p {
agents[i] = agent.AgentAcc
}
return agents, nil
}
// FindMany 查询所有
func FindMany(filter bson.M, _ *options.FindOptions, skip int64, size int64) ([]*User, error) {
sort := bson.D{{Key: "createdAt", Value: -1}}
opts := options.FindOptions{}
opts.SetSort(sort).SetSkip(skip).SetLimit(size)
data := make([]*User, 0)
if err := coll(nil).Find(&data, filter, &opts); err != nil {
log.ZapLog.Warn("proxy FindMany Find fail", log.E(err))
return data, err
}
return data, nil
}
// ExportFindMany 导出的时候查询所有
func ExportFindMany(filter bson.M, skip int64, size int64) ([]*User, error) {
sort := bson.D{{Key: "createdAt", Value: 1}}
filter["userType"] = bson.M{"$in": []UserType{UserUnkown, UserLoufengAgent}}
opts := options.FindOptions{}
opts.SetSort(sort).SetSkip(skip).SetLimit(size)
data := make([]*User, 0)
if err := coll(nil).Find(&data, filter, &opts); err != nil {
log.ZapLog.Warn(" ExportFindMany Find fail", log.E(err))
return data, err
}
return data, nil
}
// GetTotalCnt 查询数量
func GetTotalCnt(filter bson.M, _ *options.FindOptions) (total int64, err error) {
total, err = coll(nil).Count(filter)
if err != nil {
log.ZapLog.Warn("GetTotalCnt fail", log.E(err))
}
return
}
// PayUIDCountByCreatedTime 消费包括 购买VIP、付费视屏
func PayUIDCountByCreatedTime(start time.Time, end time.Time) ([]uint64, error) {
//购买VIP /分
vUIDList, err := prdcthsomod.PayVipUIDSByCreatedTime(start, end)
if err != nil {
return nil, err
}
//付费视屏 /金币
pUIDList, err := payvidlgmod.GetUIDListByPayTime(start, end)
if err != nil {
return nil, err
}
payUIDMap := make(map[uint64]byte, len(vUIDList)+len(pUIDList))
//转载payUIDMap
for _, uid := range vUIDList {
payUIDMap[uid] = 1
}
for _, uid := range pUIDList {
payUIDMap[uid] = 1
}
//取出payUID
payUIDList := make([]uint64, len(payUIDMap))
i := 0
for k := range payUIDMap {
payUIDList[i] = k
i++
}
return payUIDList, nil
}
// NewAndPayUIDListByCreatedTime 消费包括 购买VIP、付费视屏
func NewAndPayUIDListByCreatedTime(start time.Time, end time.Time) ([]uint64, error) {
uidList, err := FindUIDByCreateTime(start, end)
if err != nil {
return nil, err
}
vUIDList, err := prdcthsomod.PayVipUIDSByCreatedTime(start, end)
if err != nil {
return nil, err
}
pUIDList, err := payvidlgmod.GetUIDListByPayTime(start, end)
if err != nil {
return nil, err
}
payUIDMap := make(map[uint64]bool, len(uidList))
//转载payUIDMap
for _, uid := range pUIDList {
payUIDMap[uid] = false
}
//标记消费的的UID
for _, uid := range vUIDList {
if _, ok := payUIDMap[uid]; ok {
payUIDMap[uid] = true
}
}
for _, uid := range pUIDList {
if _, ok := payUIDMap[uid]; ok {
payUIDMap[uid] = true
}
}
//取出payUID
payUIDList := make([]uint64, 0, len(payUIDMap))
for k, v := range payUIDMap {
if v {
payUIDList = append(payUIDList, k)
}
}
return payUIDList, nil
}
type UpdateFreeTimesCond struct {
UID uint64 `json:"uid" bson:"uid" binding:"required"` // 用户ID
FreeTimes *int64 `json:"freeTimes" bson:"freeTimes"` // 免费次数
Mark *string `json:"mark" bson:"mark"` // 备注
}
func (receiver *UpdateFreeTimesCond) Filter() bson.M {
return bson.M{"uid": receiver.UID, "aiUndressFreeTimes": bson.M{"$gte": receiver.FreeTimes}}
}
func (receiver *UpdateFreeTimesCond) Update() bson.M {
var update = bson.M{}
if receiver.FreeTimes != nil {
update["aiUndressFreeTimes"] = receiver.FreeTimes
}
return bson.M{"$inc": update}
}
type UpdateDownloadCond struct {
UID uint64 `json:"uid" bson:"uid" binding:"required"` // 用户ID
Download *int64 `json:"download" bson:"download"` // 下载次数
Mark *string `json:"mark" bson:"mark"` // 备注
}
func (receiver *UpdateDownloadCond) Filter() bson.M {
return bson.M{"uid": receiver.UID}
}
func (receiver *UpdateDownloadCond) Update() bson.M {
var update = bson.M{}
if receiver.Download != nil {
update["downloadCount"] = receiver.Download
}
return bson.M{"$inc": update}
}
type SetAdvertiseCond struct {
UId []uint64 `form:"uid" json:"uid" binding:"required"` // 用户uid
Enable *bool `form:"enable" json:"enable" binding:"required"` // 是否开启
}
type UpdateLotteryTimesCond struct {
UID uint64 `json:"uid" bson:"uid" binding:"required"` // 用户ID
LotteryTimes *int64 `json:"lotteryTimes" bson:"lotteryTimes"` // 抽空次数
Mark *string `json:"mark" bson:"mark"` // 备注
}
func (receiver *UpdateLotteryTimesCond) Filter() bson.M {
return bson.M{"uid": receiver.UID}
}
func (receiver *UpdateLotteryTimesCond) Update() bson.M {
var update = bson.M{}
if receiver.LotteryTimes != nil {
update["lotteryTimes"] = receiver.LotteryTimes
}
return bson.M{"$inc": update}
}
type DeleteCommentCond struct {
UID uint64 `json:"uid" bson:"uid" binding:"required"` // 用户ID
}
type QueryGameCodeCond struct {
UID uint64 `form:"uid" json:"uid" bson:"uid,omitempty"` //用户ID
}
func (receiver *DeleteCommentCond) Filter() bson.M {
return bson.M{"uid": receiver.UID}
}