190 lines
5.5 KiB
Go
190 lines
5.5 KiB
Go
package srv_user
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"time"
|
||
"videoweb/pub/common/srvdef"
|
||
"videoweb/pub/models/cointransmod"
|
||
"videoweb/pub/models/rechargepackagemod"
|
||
"videoweb/pub/models/usermod"
|
||
"videoweb/pub/payment"
|
||
"videoweb/pub/pubdef"
|
||
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/mongo/options"
|
||
"golang.org/x/crypto/bcrypt"
|
||
)
|
||
|
||
// GetUsers 分页查询用户列表
|
||
func GetUsers(ctx context.Context, req *UserListReq) (*UserListResp, *srvdef.SrvErr) {
|
||
filter := bson.M{}
|
||
if req.Id != nil && *req.Id > 0 {
|
||
filter[usermod.FN.User.Id] = *req.Id
|
||
}
|
||
if accountNo := strings.TrimSpace(req.AccountNo); accountNo != "" {
|
||
filter[usermod.FN.User.AccountNo] = accountNo
|
||
}
|
||
if email := strings.TrimSpace(req.Email); email != "" {
|
||
filter[usermod.FN.User.Email] = email
|
||
}
|
||
if req.NickName != "" {
|
||
filter[usermod.FN.User.NickName] = bson.M{"$regex": req.NickName}
|
||
}
|
||
if districtCode := strings.TrimSpace(req.DistrictCode); districtCode != "" {
|
||
filter[usermod.FN.User.DistrictCode] = districtCode
|
||
}
|
||
if req.Status != nil {
|
||
filter[usermod.FN.User.Status] = *req.Status
|
||
}
|
||
if req.Type != nil {
|
||
filter[usermod.FN.User.Type] = *req.Type
|
||
}
|
||
|
||
opts := options.Find().
|
||
SetSort(bson.M{usermod.FN.User.Id: -1}).
|
||
SetSkip((req.PageNum - 1) * req.PageSize).
|
||
SetLimit(req.PageSize)
|
||
|
||
resp := &UserListResp{}
|
||
total, serr := usermod.CountUsers(ctx, filter)
|
||
if serr != nil {
|
||
return resp, serr
|
||
}
|
||
resp.Total = total
|
||
if total == 0 {
|
||
return resp, nil
|
||
}
|
||
list, serr := usermod.FindManyUser(ctx, filter, opts)
|
||
if serr != nil {
|
||
return resp, serr
|
||
}
|
||
resp.List = list
|
||
return resp, nil
|
||
}
|
||
|
||
// UpdateUser 更新指定用户信息
|
||
func UpdateUser(ctx context.Context, id int64, param usermod.UserUpdate) *srvdef.SrvErr {
|
||
now := time.Now()
|
||
param.UpdatedAt = &now
|
||
return usermod.UpdateOneUser(ctx,
|
||
bson.M{usermod.FN.User.Id: id},
|
||
bson.M{"$set": param},
|
||
)
|
||
}
|
||
|
||
// GiftCoin 给用户赠送/扣除金币,并写入流水记录
|
||
// coin 为正数时增加余额,为负数时扣减(不校验余额是否充足,由调用方确保)
|
||
func GiftCoin(ctx context.Context, req *GiftCoinReq) *srvdef.SrvErr {
|
||
if req.Coin == 0 {
|
||
return srvdef.New(srvdef.ErrParamError, "金币数量不能为 0", "")
|
||
}
|
||
|
||
// 原子更新余额
|
||
if serr := usermod.UpdateOneUser(ctx,
|
||
bson.M{usermod.FN.User.Id: req.UserId},
|
||
bson.M{
|
||
"$inc": bson.M{usermod.FN.User.Recharge: req.Coin},
|
||
"$set": bson.M{usermod.FN.User.UpdatedAt: time.Now()},
|
||
},
|
||
); serr != nil {
|
||
return serr
|
||
}
|
||
|
||
// 写流水(更新后再查余额,作为 balanceAfter)
|
||
flowDir := cointransmod.FlowIn
|
||
amount := req.Coin
|
||
if req.Coin < 0 {
|
||
flowDir = cointransmod.FlowOut
|
||
amount = -req.Coin
|
||
}
|
||
remark := req.Remark
|
||
if remark == "" {
|
||
remark = payment.CoinTransRemark(cointransmod.TranTypeAdminAdjust, "")
|
||
}
|
||
_ = payment.WriteCoinTransByUser(ctx, payment.WriteCoinTransOpt{
|
||
UserId: req.UserId,
|
||
FlowDir: flowDir,
|
||
TranType: cointransmod.TranTypeAdminAdjust,
|
||
Amount: amount,
|
||
Remark: remark,
|
||
})
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateVip 根据会员畅享套餐修改用户会员信息。
|
||
func UpdateVip(ctx context.Context, req *UpdateVipReq) *srvdef.SrvErr {
|
||
if req == nil || req.UserId <= 0 || req.PackageId <= 0 {
|
||
return srvdef.New(srvdef.ErrParamError, "用户ID和套餐ID不能为空", "")
|
||
}
|
||
user, serr := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: req.UserId})
|
||
if serr != nil || user == nil {
|
||
return srvdef.New(srvdef.ErrUserNotExsit, "", "")
|
||
}
|
||
pkg, serr := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{
|
||
rechargepackagemod.FN.RechargePackage.Id: req.PackageId,
|
||
rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip,
|
||
rechargepackagemod.FN.RechargePackage.Status: rechargepackagemod.StatusEnabled,
|
||
})
|
||
if serr != nil || pkg == nil {
|
||
return srvdef.New(srvdef.ErrNoData, "会员套餐不存在或未启用", "")
|
||
}
|
||
if pkg.DurationDays <= 0 {
|
||
return srvdef.New(srvdef.ErrParamError, "会员套餐有效天数无效", "")
|
||
}
|
||
|
||
now := time.Now()
|
||
baseTime := now
|
||
if user.CurrentVipExpire.After(now) {
|
||
baseTime = user.CurrentVipExpire
|
||
}
|
||
newExpire := baseTime.Add(time.Duration(pkg.DurationDays) * 24 * time.Hour)
|
||
updateDoc := bson.M{
|
||
"$set": bson.M{
|
||
usermod.FN.User.VipType: vipTypeFromDurationDays(pkg.DurationDays),
|
||
usermod.FN.User.ShowCardId: pkg.Id,
|
||
usermod.FN.User.ShowCardName: pkg.Name,
|
||
usermod.FN.User.CurrentVipExpire: newExpire,
|
||
usermod.FN.User.UpdatedAt: now,
|
||
},
|
||
}
|
||
|
||
if serr = usermod.UpdateOneUser(ctx, bson.M{usermod.FN.User.Id: req.UserId}, updateDoc); serr != nil {
|
||
return serr
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ResetPassword 按用户ID重置登陆密码,入库为 bcrypt 哈希,与 app 登录校验一致。
|
||
func ResetPassword(ctx context.Context, req *ResetPasswordReq) *srvdef.SrvErr {
|
||
if req == nil || req.UserId <= 0 {
|
||
return srvdef.New(srvdef.ErrParamError, "用户ID不能为空", "")
|
||
}
|
||
password := strings.TrimSpace(req.Password)
|
||
if password == "" {
|
||
return srvdef.New(srvdef.ErrParamError, "密码不能为空", "")
|
||
}
|
||
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
return srvdef.New(srvdef.Failure, "密码处理失败", "")
|
||
}
|
||
return usermod.UpdateOneUser(ctx,
|
||
bson.M{usermod.FN.User.Id: req.UserId},
|
||
bson.M{"$set": bson.M{
|
||
usermod.FN.User.Password: string(hashed),
|
||
usermod.FN.User.UpdatedAt: time.Now(),
|
||
}},
|
||
)
|
||
}
|
||
|
||
func vipTypeFromDurationDays(days int64) pubdef.VipType {
|
||
if days >= 365 {
|
||
return pubdef.VipTypeYear
|
||
}
|
||
if days > 0 {
|
||
return pubdef.VipTypeMonth
|
||
}
|
||
return pubdef.VipTypeNone
|
||
}
|