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
+109
View File
@@ -0,0 +1,109 @@
package activityser
import (
"fmt"
"time"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/activitymod"
"91porn-server/web/webg"
)
// 新增活动
func Add(in activitymod.AddCond) stderr.Code {
result, err := activitymod.Add(in)
if err != nil {
return stderr.ErrDbInsertError
}
log.Info(fmt.Sprintf("新增活动[%v]成功", result))
return stderr.Success
}
// 修改活动
func Modify(in *activitymod.ModifyCond) stderr.Code {
var jobStatus bool
now := time.Now()
// 获取活动信息
activityDetail, err := activitymod.Get(in.ID)
if err != nil {
return stderr.ErrDbQueryError
}
// 活动开启限制
if in.Status != nil {
// 时间限制
if *in.Status && (in.StartTime.After(now) || !in.EndTime.After(now)) {
return stderr.ActivityDateError
}
// 开启/关闭任务
if (*in.Status && !activityDetail.Status) || (!*in.Status && activityDetail.Status) {
jobStatus = true
}
}
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if err = activitymod.Modify(t, in.Cond(), in.Bson()); err != nil {
return stderr.ErrDbUpdateError
}
if jobStatus {
if err = OpenBotDraw(in.ID, *in.Status); err != nil {
return err
}
}
return nil
}); err != nil {
return stderr.ErrDbTransError
}
return stderr.Success
}
// 查询列表
func QueryAll(in *activitymod.QueryAllCond) (interface{}, stderr.Code) {
var data map[string]interface{} = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := activitymod.Count(in.Query())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := activitymod.QueryAll(in.Query(), in.Options())
if err != nil {
return nil, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func QueryDropDownBox() (interface{}, stderr.Code) {
list, err := activitymod.QueryDropDownBox()
if err != nil {
return nil, stderr.ErrDbQueryError
}
data := make([]map[string]interface{}, len(list))
for i, v := range list {
data[i] = map[string]interface{}{
"label": v.Title,
"value": v.ID.Hex(),
}
}
return data, stderr.Success
}
func LoadingJob() error {
// 获取所有开启中活动
activityList, err := activitymod.QueryDropDownBox()
if err != nil {
return err
}
for _, v := range activityList {
if err = OpenBotDraw(v.ID, v.Status); err != nil {
return err
}
}
return nil
}
+131
View File
@@ -0,0 +1,131 @@
package activityser
import (
"fmt"
"math/rand"
"sync"
"time"
"91porn-server/common/log"
"91porn-server/models/v/activitymod"
"91porn-server/models/v/prizemod"
"github.com/robfig/cron/v3"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var job JobCron
type JobCron struct {
cron *cron.Cron
cache map[primitive.ObjectID]cron.EntryID
mutex sync.Mutex
}
func init() {
job = JobCron{
cron: cron.New(cron.WithSeconds()),
cache: make(map[primitive.ObjectID]cron.EntryID, 1),
}
job.cron.Start()
}
type BotDraw struct {
ActivityID primitive.ObjectID
}
func (b BotDraw) Run() {
var now time.Time = time.Now()
// 随机种子
randInt := rand.New(rand.NewSource(time.Now().UnixNano()))
// 随机获取n个马甲号
names := GetFullName(randInt)
// 获取大奖列表
prizes, err := prizemod.GetHighPrizeListByActivityID(b.ActivityID)
if err != nil {
log.Error(fmt.Sprintf("获取大奖列表失败 [%v]", err))
return
}
// 获取奖池信息
prizePool, err := activitymod.GetPrizePool(b.ActivityID)
if err != nil {
log.Error(fmt.Sprintf("获取奖池信息失败 [%v]", err))
return
}
// 增加抽奖记录
records := make([]activitymod.UserRecord, len(names))
for i, v := range names {
p := prizes[randInt.Intn(len(prizes))]
records[i] = activitymod.UserRecord{
ActivityID: b.ActivityID,
VIPCardID: p.VIPCardID,
UserName: v,
Name: p.Name,
Type: p.Type,
Price: p.Price,
Count: p.Count,
Value: p.Value,
Level: p.Level,
DrawPrice: prizePool.Price,
CreateTime: now.Add(-time.Duration(randInt.Int63n(3600)) * time.Second),
}
}
// 插入中奖信息
if err := activitymod.AddMany(records); err != nil {
log.Error(fmt.Sprintf("插入中奖信息失败 [%v]", err))
return
}
}
func GetFullName(randInt *rand.Rand) []string {
var lastName = []string{
"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋",
"沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏",
"陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭",
"郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "任", "袁", "柳", "鲍", "史", "唐", "费", "薛",
"雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "安", "常", "傅", "卞", "齐", "元", "顾", "孟",
"平", "黄", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "狄", "米", "伏", "成", "戴", "谈",
"宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "季", "贾",
"路", "娄", "江", "童", "颜", "郭", "梅", "盛", "林", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田",
"樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "管", "卢", "莫", "柯", "房", "裘", "缪", "解", "应",
"宗", "丁", "宣", "邓", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "龚", "程", "嵇", "邢",
"裴", "陆", "荣", "翁", "荀", "于", "惠", "甄", "曲", "封", "储", "仲", "伊", "宁", "仇", "甘", "武",
"符", "刘", "景", "詹", "龙", "叶", "幸", "司", "黎", "溥", "印", "怀", "蒲", "邰", "从", "索", "赖",
"卓", "屠", "池", "乔", "胥", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵",
"冉", "宰", "雍", "桑", "寿", "通", "燕", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎",
"连", "习", "容", "向", "古", "易", "廖", "庾", "终", "步", "都", "耿", "满", "弘", "匡", "国", "文",
"寇", "广", "禄", "阙", "东", "欧", "利", "师", "巩", "聂", "关", "荆", "司马", "上官", "欧阳", "夏侯",
"诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于",
"太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "徐离", "宇文", "长孙", "慕容", "司徒", "司空"}
var firstName = []string{
"伟", "刚", "勇", "毅", "俊", "峰", "强", "军", "平", "保", "东", "文", "辉", "力", "明", "永", "健", "世", "广", "志", "义",
"兴", "良", "海", "山", "仁", "波", "宁", "贵", "福", "生", "龙", "元", "全", "国", "胜", "学", "祥", "才", "发", "武", "新",
"利", "清", "飞", "彬", "富", "顺", "信", "子", "杰", "涛", "昌", "成", "康", "星", "光", "天", "达", "安", "岩", "中", "茂",
"进", "林", "有", "坚", "和", "彪", "博", "诚", "先", "敬", "震", "振", "壮", "会", "思", "群", "豪", "心", "邦", "承", "乐",
"绍", "功", "松", "善", "厚", "庆", "磊", "民", "友", "裕", "河", "哲", "江", "超", "浩", "亮", "政", "谦", "亨", "奇", "固",
"之", "轮", "翰", "朗", "伯", "宏", "言", "若", "鸣", "朋", "斌", "梁", "栋", "维", "启", "克", "伦", "翔", "旭", "鹏", "泽",
"晨", "辰", "士", "以", "建", "家", "致", "树", "炎", "德", "行", "时", "泰", "盛", "雄", "琛", "钧", "冠", "策", "腾", "楠",
"榕", "风", "航", "弘", "秀", "娟", "英", "华", "慧", "巧", "美", "娜", "静", "淑", "惠", "珠", "翠", "雅", "芝", "玉", "萍",
"红", "娥", "玲", "芬", "芳", "燕", "彩", "春", "菊", "兰", "凤", "洁", "梅", "琳", "素", "云", "莲", "真", "环", "雪", "荣",
"爱", "妹", "霞", "香", "月", "莺", "媛", "艳", "瑞", "凡", "佳", "嘉", "琼", "勤", "珍", "贞", "莉", "桂", "娣", "叶", "璧",
"璐", "娅", "琦", "晶", "妍", "茜", "秋", "珊", "莎", "锦", "黛", "青", "倩", "婷", "姣", "婉", "娴", "瑾", "颖", "露", "瑶",
"怡", "婵", "雁", "蓓", "纨", "仪", "荷", "丹", "蓉", "眉", "君", "琴", "蕊", "薇", "菁", "梦", "岚", "苑", "婕", "馨", "瑗",
"琰", "韵", "融", "园", "艺", "咏", "卿", "聪", "澜", "纯", "毓", "悦", "昭", "冰", "爽", "琬", "茗", "羽", "希", "欣", "飘",
"育", "滢", "馥", "筠", "柔", "竹", "霭", "凝", "晓", "欢", "霄", "枫", "芸", "菲", "寒", "伊", "亚", "宜", "可", "姬", "舒",
"影", "荔", "枝", "丽", "阳", "妮", "宝", "贝", "初", "程", "梵", "罡", "恒", "鸿", "桦", "骅", "剑", "娇", "纪", "宽", "苛",
"灵", "玛", "媚", "琪", "晴", "容", "睿", "烁", "堂", "唯", "威", "韦", "雯", "苇", "萱", "阅", "彦", "宇", "雨", "洋", "忠",
"宗", "曼", "紫", "逸", "贤", "蝶", "菡", "绿", "蓝", "儿", "翠", "烟", "小", "轩"}
var lastNameLen = len(lastName)
var firstNameLen = len(firstName)
total := randInt.Intn(10)
names := make([]string, total+1)
// 随机产生1-10位
for i := 0; i <= total; i++ {
var first string
for i := 0; i <= rand.Intn(2); i++ { //随机产生2位或者3位的名
first = fmt.Sprint(firstName[rand.Intn(firstNameLen-1)])
}
names[i] = fmt.Sprintf("%s%s", fmt.Sprint(lastName[rand.Intn(lastNameLen-1)]), first)
}
return names
}
+81
View File
@@ -0,0 +1,81 @@
package activityser
import (
"fmt"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/prizemod"
"91porn-server/models/v/productmod"
)
// 新增活动
func AddPrize(in prizemod.AddPrizeCond) stderr.Code {
result, err := prizemod.AddPrize(in)
if err != nil {
return stderr.ErrDbInsertError
}
log.Info(fmt.Sprintf("新增活动[%v]成功", result))
return stderr.Success
}
// 修改活动
func ModifyPrize(in *prizemod.ModifyPrizeCond) stderr.Code {
if err := prizemod.ModifyPrize(in.Cond(), in.Bson()); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// 查询列表
func QueryAllPrize(in *prizemod.QueryAllPrizeCond) (interface{}, stderr.Code) {
var data map[string]interface{} = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := prizemod.CountPrize(in.Query())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := prizemod.QueryAllPrize(in.Query(), in.Options())
if err != nil {
return nil, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func QueryVipDropDownBox() (interface{}, stderr.Code) {
list, err := productmod.FindByProductType(commod.VIP)
if err != nil {
return nil, stderr.ErrDbQueryError
}
data := make([]map[string]interface{}, len(list))
for i, v := range list {
data[i] = map[string]interface{}{
"label": v.Name,
"value": v.ID.Hex(),
}
}
return data, stderr.Success
}
func QueryPrizeDropDownBox() (interface{}, stderr.Code) {
list, err := prizemod.QueryDropDownBox()
if err != nil {
return nil, stderr.ErrDbQueryError
}
data := make([]map[string]interface{}, len(list))
for i, v := range list {
data[i] = map[string]interface{}{
"label": v.Name + fmt.Sprintf("[数量:%d]", v.Count),
"value": v.ID.Hex(),
}
}
return data, stderr.Success
}
+49
View File
@@ -0,0 +1,49 @@
package activityser
import (
"fmt"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/activitymod"
)
// 新增活动奖池
func AddPrizePool(in activitymod.AddPrizePoolCond) stderr.Code {
result, err := activitymod.AddPrizePool(in)
if err != nil {
return stderr.ErrDbInsertError
}
log.Info(fmt.Sprintf("新增活动奖池[%v]成功", result))
return stderr.Success
}
// 修改活动奖池
func ModifyPrizePool(in *activitymod.ModifyPrizePoolCond) stderr.Code {
if err := activitymod.ModifyPrizePool(in.Cond(), in.Bson()); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// 查询活动奖池列表
func QueryAllPrizePool(in *activitymod.QueryAllPrizePoolCond) (interface{}, stderr.Code) {
var data map[string]interface{} = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := activitymod.CountPrizePool(in.Query())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := activitymod.QueryAllPrizePool(in.Query(), in.Options())
if err != nil {
return nil, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
+55
View File
@@ -0,0 +1,55 @@
package activityser
import (
"errors"
"91porn-server/common/stderr"
"91porn-server/models/v/activitymod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func QueryAllRecord(in *activitymod.QueryAllRecordCond) (interface{}, stderr.Code) {
var data map[string]interface{} = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := activitymod.CountRecord(in.Query())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := activitymod.QueryAllRecord(in.Query(), in.Options())
if err != nil {
return nil, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
// 开启机器人抽奖
func OpenBotDraw(activityID primitive.ObjectID, status bool) error {
job.mutex.Lock()
defer job.mutex.Unlock()
if entryID, ok := job.cache[activityID]; ok {
if !status {
// 移除任务
job.cron.Remove(entryID)
delete(job.cache, activityID)
return nil
}
return errors.New("任务已开启")
}
if status {
// 加入任务(每小时执行一次)
entryID, err := job.cron.AddJob("@hourly", BotDraw{ActivityID: activityID})
if err != nil {
return err
}
job.cache[activityID] = entryID
}
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package actvser
import (
"time"
"91porn-server/models/v/actmod"
"go.mongodb.org/mongo-driver/bson"
)
func GetActities(req GetActitiesRequest) (int64, []actmod.Activity, bool, error) {
skip := (req.PageNum - 1) * req.PageSize
count, err := actmod.GetActivitiesCount()
if err != nil {
return 0, nil, false, err
}
if uint64(count) <= skip {
return 0, nil, false, err
}
limit := req.PageSize + 1
actvs, err := actmod.GetActivities(skip, limit)
if err != nil {
return count, nil, false, err
}
hasNext := false
if uint64(len(actvs)) > req.PageSize {
hasNext = true
actvs = actvs[:req.PageSize]
}
return count, actvs, hasNext, nil
}
func AddActivity(req AddActivityRequest) error {
now := time.Now()
act := actmod.Activity{
CreatedAt: now,
UpdatedAt: now,
}
if req.ActivityName != nil {
act.ActivityName = *req.ActivityName
}
if req.Desc != nil {
act.Desc = *req.Desc
}
if req.Img != nil {
act.Img = *req.Img
}
if req.Content != nil {
act.Content = *req.Content
}
if req.Link != nil {
act.Link = *req.Link
}
if req.Sort != nil {
act.Sort = *req.Sort
}
if req.Status != nil {
act.Status = *req.Status
}
if req.ExpiredIn != nil {
act.ExpiredIn = *req.ExpiredIn
}
return actmod.AddACtivity(act)
}
func UpdateActivity(req UpdateActivityRequest) error {
b := make(bson.M)
if req.ActivityName != nil {
b["activityName"] = *req.ActivityName
}
if req.Desc != nil {
b["desc"] = *req.Desc
}
if req.Img != nil {
b["img"] = *req.Img
}
if req.Content != nil {
b["content"] = *req.Content
}
if req.Link != nil {
b["link"] = *req.Link
}
if req.Sort != nil {
b["sort"] = *req.Sort
}
if req.Status != nil {
b["status"] = *req.Status
}
if req.ExpiredIn != nil {
b["expiredIn"] = *req.ExpiredIn
}
if len(b) == 0 {
return nil
}
now := time.Now()
b["createdAt"] = now
b["updatedAt"] = now
return actmod.EidtActivity(req.ActvID, b)
}
+35
View File
@@ -0,0 +1,35 @@
package actvser
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type GetActitiesRequest struct {
PageNum uint64 `form:"pageNum" json:"pageNum" binding:"required,min=1"` // 页码
PageSize uint64 `form:"pageSize" json:"pageSize" binding:"required,min=1,max=20"` // 每页展示数
}
type UpdateActivityRequest struct {
ActvID primitive.ObjectID `json:"activeID"` // 被编辑的活动id
ActivityName *string `json:"activityName"` // 活动名
Desc *string `json:"desc"` // 活动说明
Img *string `json:"img"` // 活动图片
Content *string `json:"content"` // 富文本内容
Link *string `json:"link"` // 链接地址
Sort *int `json:"sort"` // 活动排序
Status *int `json:"status"` // 状态 1 有效 0 无效
ExpiredIn *time.Time `json:"expiredIn"` // 过期时间
}
type AddActivityRequest struct {
ActivityName *string `json:"activityName"` // 活动名
Desc *string `json:"desc"` // 活动说明
Img *string `json:"img"` // 活动图片
Content *string `json:"content"` // 富文本内容
Link *string `json:"link"` // 链接地址
Sort *int `json:"sort"` // 活动排序
Status *int `json:"status"` // 状态 1 有效 0 无效
ExpiredIn *time.Time `json:"expiredIn"` // 过期时间
}
+42
View File
@@ -0,0 +1,42 @@
package addwelfareser
import (
"strconv"
"strings"
"time"
"91porn-server/common/stderr"
"91porn-server/models/l/welfarelgmod"
"91porn-server/models/v/usermod"
"91porn-server/web/service/userser"
"github.com/jinzhu/now"
)
func AddWelfare(userId string) (stderr.Code, error) {
uid, err := strconv.ParseUint(userId, 10, 64)
if err != nil {
return stderr.Failure, err
}
user, err := usermod.FindUserByUIDForNoCache(uid)
if err != nil || user == nil {
return stderr.Failure, err
}
if err = welfarelgmod.InsertWelFareLog(welfarelgmod.WelfareLog{
Uid: uid,
FareType: welfarelgmod.Fare_Vip,
FareNum: 2,
DailyDate: now.BeginningOfDay(),
CreatedAt: time.Now(),
}); err != nil {
if strings.Contains(err.Error(), "E11000 duplicate key error") {
return stderr.ErrDbInputExist, err
}
return stderr.Failure, err
}
vipExpire, vipLevel := userser.CheckVipRenew(user, 3, 0)
if _, err := usermod.Update(user.UID, usermod.UserSelector{VipExpireDate: &vipExpire, VipLevel: &vipLevel}); err != nil {
return stderr.Failure, err
}
return stderr.Success, nil
}
+97
View File
@@ -0,0 +1,97 @@
package adminser
import (
"time"
"91porn-server/common/pageopt"
"91porn-server/models/v/adminmod"
"91porn-server/models/v/authoritymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type M = bson.M
// Record Record
type Record struct {
ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
Name *string `json:"name,omitempty" bson:"name,omitempty"` //账号
Password *string `json:"password,omitempty" bson:"password,omitempty"` //密码
Nickname *string `json:"nickname,omitempty" bson:"nickname,omitempty"` //昵称
Email *string `json:"email,omitempty" bson:"email,omitempty"` //邮箱
PrivilegeID *primitive.ObjectID `json:"privilegeID,omitempty" bson:"privilegeID,omitempty"` //权限ID
Role *string `json:"role,omitempty" bson:"role,omitempty"` //角色
HasLocked *bool `json:"hasLocked,omitempty" bson:"hasLocked,omitempty"` //已禁止登陆
LockReason *string `json:"lockReason,omitempty" bson:"lockReason,omitempty"` //禁止登陆的原因
CreatedAt *time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"`
LastLoginAt *time.Time `json:"lastLoginAt,omitempty" bson:"lastLoginAt,omitempty"`
}
type Page struct {
Total int64 `json:"total" bson:"total"`
List []Record `json:"list" bson:"list"`
}
// NameMatch
type NameMatch struct {
Name *string
}
func (n *NameMatch) New() pageopt.Matcher {
return pageopt.NewAssignMatch("name", n.Name)
}
// NotSuperAdminMatch
type NotSuperAdminMatch struct {
}
func (n *NotSuperAdminMatch) New() pageopt.Matcher {
return pageopt.NewAssignMatch("name", M{"$ne": "superAdmin"})
}
// AdminPages AdminPages
func AdminPages(skip, limit int64, name *string) (Page, error) {
matList := []pageopt.Matcher{(&NotSuperAdminMatch{}).New()}
if name != nil {
matList = []pageopt.Matcher{(&NameMatch{name}).New()}
}
adminList, err := adminmod.List(skip, limit, matList...)
if err != nil {
return Page{}, err
}
roles := make([]string, len(adminList))
for i, admin := range adminList {
roles[i] = admin.Role
}
roleIDMap, err := authoritymod.IDMapByRoles(roles)
if err != nil {
return Page{}, err
}
recordList := make([]Record, len(adminList))
for i, admin := range adminList {
_admin := admin
privilegeID := roleIDMap[_admin.Role]
recordList[i] = Record{
ID: _admin.ID,
Name: &_admin.Name,
Password: &_admin.Password,
Nickname: &_admin.Nickname,
Email: &_admin.Email,
PrivilegeID: &privilegeID,
Role: &_admin.Role,
HasLocked: &_admin.HasLocked,
LockReason: &_admin.LockReason,
CreatedAt: &_admin.CreatedAt,
LastLoginAt: &_admin.LastLoginAt,
}
}
count, err := adminmod.Count(matList...)
if err != nil {
return Page{}, err
}
return Page{
Total: count,
List: recordList,
}, nil
}
+111
View File
@@ -0,0 +1,111 @@
package adser
import (
"time"
"91porn-server/common"
"91porn-server/models/v/adsmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type AdRecord struct {
ID primitive.ObjectID `json:"id"` //广告id
AdsType adsmod.AdsType `json:"adsType"` //广告类型
Cover string `json:"cover"` //广告封面图
Size int `json:"size"` //尺寸 B
Height int `json:"height"` //高
Width int `json:"width"` //宽
Title string `json:"title"` //广告标题
HrefType adsmod.URLJumpType `json:"hrefType"` //广告链接类型
Href string `json:"href"` //广告跳转地址
Position adsmod.AdPosition `json:"position"` //广告位置
SortCode int `json:"sortCode"` //排序号
Active bool `json:"active"` //激活状态 true:激活
Click int `json:"click"` //点击次数
Remark string `json:"remark"` //备注
ReviewID primitive.ObjectID `json:"reviewID,omitempty"` //审核id
DistrictCode string `json:"districtCode"` //商区码
CreatedAt time.Time `json:"createdAt"` //创建时间
Start time.Time `json:"start"` //开始时间
End time.Time `json:"end"` //结束时间
}
type AdPage struct {
List []AdRecord `json:"list"`
Total int64 `json:"total"`
}
// AdPages 获取广告列表
func AdPages(skip, limit int64, idMatch adsmod.IDMatch, adsType adsmod.AdsTypeMatch, adsPositionMatch adsmod.AdPositionMatch, districtCodeMatch adsmod.DistrictCodeMatch) (AdPage, error) {
mats := []adsmod.Matcher{
idMatch.New(),
adsType.New(),
adsPositionMatch.New(),
districtCodeMatch.New(),
}
list, _ := adsmod.List(adsmod.Sort_createdAt_N1, &skip, &limit, mats...)
recordList := make([]AdRecord, len(list))
for i, v := range list {
recordList[i] = AdRecord{
ID: v.ID,
AdsType: v.AdsType,
Cover: v.Cover,
Size: v.Size,
Height: v.Height,
Width: v.Width,
Title: v.Title,
HrefType: v.HrefType,
Href: v.Href,
Position: v.Position,
SortCode: v.SortCode,
Active: v.Active,
Click: v.Click,
Remark: v.Remark,
ReviewID: v.ReviewID,
DistrictCode: v.DistrictCode,
CreatedAt: v.CreatedAt,
Start: v.Start,
End: v.End,
}
}
count, _ := adsmod.Count(mats...)
return AdPage{
recordList,
count,
}, nil
}
func IsValidAdPicture(pos adsmod.AdPosition, pictureProp common.PictureProp) error {
switch pos {
case adsmod.StartupPage: //启动页广告
if pictureProp.Height > 1560 || pictureProp.Width > 720 {
return ErrImgScale{}
}
case adsmod.HomeRecoSmall: //首页-推荐小广告
if pictureProp.Height > 620 || pictureProp.Width > 560 {
return ErrImgScale{}
}
case adsmod.HomePopupAnnou: //首页-弹窗公告
if pictureProp.Height > 1300 || pictureProp.Width > 656 {
return ErrImgScale{}
}
case adsmod.NotifyPage: //消息-界面广告
if pictureProp.Height > 260 || pictureProp.Width > 720 {
return ErrImgScale{}
}
case adsmod.LiaoCarousel: //撩吧-轮播广告
if pictureProp.Height > 500 || pictureProp.Width > 720 {
return ErrImgScale{}
}
case adsmod.WalletPage: //钱包-轮播广告
if pictureProp.Height > 120 || pictureProp.Width > 720 {
return ErrImgScale{}
}
case adsmod.SearchPage: //搜索-轮播广告
if pictureProp.Height > 360 || pictureProp.Width > 720 {
return ErrImgScale{}
}
}
return nil
}
+29
View File
@@ -0,0 +1,29 @@
package adser
type ErrImgURLInvalid struct {
}
func (ErrImgURLInvalid) Error() string {
return "picture url is invalid!"
}
type ErrImgTooBig struct {
}
func (ErrImgTooBig) Error() string {
return "picture is too big!"
}
type ErrImgScale struct {
}
func (ErrImgScale) Error() string {
return "picture cale is invalid!"
}
type ErrImgContent struct {
}
func (ErrImgContent) Error() string {
return "picture is invalid!"
}
+166
View File
@@ -0,0 +1,166 @@
package adser
import (
"time"
"91porn-server/common/db"
"91porn-server/models/v/adreviewmod"
"91porn-server/models/v/adsmod"
"91porn-server/web/webg"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ReviewRecord struct {
ID primitive.ObjectID `json:"id"` //id
DistrictCode string `json:"districtCode"` //商区码
OptType adreviewmod.OptType `json:"optType"` //操作类型
ApplyAt time.Time `json:"applyAt"` //申请时间
PassAt time.Time `json:"passAt"` //通过时间
RefussAt time.Time `json:"refussAt"` //拒绝时间
Reviewer string `json:"reviewer"` //审核人
ReviewStatus adreviewmod.ReviewStatus `json:"reviewStatus"` //审核状态
Remark string `json:"remark"` //备注
ReceiptInfo string `json:"receiptInfo"` //回执信息
ADID primitive.ObjectID `json:"adID"` //广告id
Title string `json:"title"` //广告标题
Cover string `json:"cover"` //广告封面图
Size int `json:"size"` //尺寸 单位:B
Height int `json:"height"`
Width int `json:"width"`
HrefType adsmod.URLJumpType `json:"hrefType"` //广告链接类型
Href string `json:"href"` //广告跳转地址
Position adsmod.AdPosition `json:"position"` //广告位置
SortCode int `json:"sortCode"` //排序号
}
type ReviewPage struct {
List []ReviewRecord `json:"list"`
Total int64 `json:"total"`
}
// ReviewPages 获取广告列表
func ReviewPages(skip, limit int64, idMatch adreviewmod.IDMatch, reviewStatusMatch adreviewmod.ReviewStatusMatch, districtCodeMatch adreviewmod.DistrictCodeMatch) (ReviewPage, error) {
mats := []adreviewmod.Matcher{
idMatch.New(),
reviewStatusMatch.New(),
districtCodeMatch.New(),
}
list, err := adreviewmod.List(adreviewmod.Sort_createdAt_N1, &skip, &limit, mats...)
if err != nil {
return ReviewPage{}, nil
}
recordList := make([]ReviewRecord, len(list))
for i, v := range list {
recordList[i] = ReviewRecord{
ID: v.ID,
DistrictCode: v.DistrictCode,
OptType: v.OptType,
PassAt: v.PassAt,
RefussAt: v.RefussAt,
Reviewer: v.Reviewer,
ReviewStatus: v.ReviewStatus,
ADID: v.ADID,
Cover: v.Cover,
Size: v.Size,
Height: v.Height,
Width: v.Width,
Title: v.Title,
HrefType: v.HrefType,
Href: v.Href,
Position: adsmod.AdPosition(v.Position),
SortCode: v.SortCode,
Remark: v.Remark,
ReceiptInfo: v.ReceiptInfo,
ApplyAt: v.ApplyAt,
}
}
count, err := adreviewmod.Count(mats...)
if err != nil {
return ReviewPage{}, nil
}
return ReviewPage{
recordList,
count,
}, nil
}
func CheckRefuse(id primitive.ObjectID, reviewer string, receiptInfo string) error {
return adreviewmod.UpdateReviewByID(id, adreviewmod.Processong, adreviewmod.Refuse, nil, reviewer, receiptInfo, time.Now())
}
func CheckPass(id primitive.ObjectID, adsType adsmod.AdsType, reviewer string, receiptInfo string, startTime, endTime time.Time) error {
adR, err := adreviewmod.FindOneByID(id)
if err != nil {
return err
}
now := time.Now()
position := adsmod.AdPosition(adR.Position)
reviewInfo := adsmod.ReviewInfo{
ReviewID: adR.ID,
ReviewAt: &now,
Reviewer: &reviewer,
}
insDoc := adsmod.InsertDoc{
AdsType: adsType,
Cover: adR.Cover,
Title: adR.Title,
HrefType: adR.HrefType,
Href: adR.Href,
Position: position,
SortCode: adR.SortCode,
Active: true,
Remark: receiptInfo,
DistrictCode: adR.DistrictCode,
ReviewInfo: reviewInfo,
CreatedAt: now,
CoverProp: adR.CoverProp,
Start: startTime,
End: endTime,
}
t := true
upDoc := adsmod.UpdateDoc{
Title: &adR.Title,
Cover: &adR.Cover,
HrefType: &adR.HrefType,
Href: &adR.Href,
Position: &position,
SortCode: &adR.SortCode,
Active: &t,
Remark: &receiptInfo,
ReviewInfo: reviewInfo,
UpdatedAt: now,
CoverProp: adR.CoverProp,
Start: &startTime,
End: &endTime,
}
return webg.VideoDB.Trans(func(t *db.MongoTool) error {
if adR.ADID.IsZero() {
adID, err := adsmod.InsertOne(t, insDoc)
if err != nil {
return err
}
return adreviewmod.UpdateReviewByID(id, adreviewmod.Processong, adreviewmod.Pass, &adID, reviewer, receiptInfo, now)
}
if err := adsmod.UpdateOneByID(t, adR.ADID, upDoc); err != nil {
return err
}
return adreviewmod.UpdateReviewByID(id, adreviewmod.Processong, adreviewmod.Pass, nil, reviewer, receiptInfo, now)
})
}
func DeleteAd(adID, arID *primitive.ObjectID) error {
return webg.VideoDB.Trans(func(t *db.MongoTool) error {
if adID != nil {
if err := adsmod.DeleteOne(t, *adID); err != nil {
return err
}
}
if arID != nil {
if err := adreviewmod.DeleteOne(t, *arID); err != nil {
return err
}
}
return nil
})
}
@@ -0,0 +1,50 @@
package advance_config_ser
import (
"91porn-server/common/stderr"
"91porn-server/models/v/advanceconfigmod"
)
func QueryAll(in *advanceconfigmod.QueryAllCond) (*advanceconfigmod.QueryAllRes, error) {
var out = &advanceconfigmod.QueryAllRes{}
count, err := advanceconfigmod.CountDocument(in.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*advanceconfigmod.AdvanceConfig{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = advanceconfigmod.QueryAllDocument(in.Filter(), in.Options())
if err != nil {
return nil, err
}
return out, nil
}
func Create(in *advanceconfigmod.WebCreateReq, manager string) stderr.Code {
err := advanceconfigmod.InsertOne(nil, in.Create(manager))
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
func Update(in *advanceconfigmod.WebUpdateReq, manager string) stderr.Code {
err := advanceconfigmod.Update(nil, in.Filter(), in.Update(manager))
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
func Del(in *advanceconfigmod.WebDeleteReq, manager string) stderr.Code {
err := advanceconfigmod.Update(nil, in.Filter(), in.Del(manager))
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
@@ -0,0 +1,24 @@
package advance_order_ser
import (
"91porn-server/models/v/advanceordermod"
)
func QueryAll(in *advanceordermod.QueryAllCond) (*advanceordermod.QueryAllRes, error) {
var out = &advanceordermod.QueryAllRes{}
count, err := advanceordermod.CountDocument(in.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*advanceordermod.AdvanceOrder{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = advanceordermod.QueryAllDocument(in.Filter(), in.Options())
if err != nil {
return nil, err
}
return out, nil
}
+270
View File
@@ -0,0 +1,270 @@
package advgroupser
import (
"91porn-server/common"
"91porn-server/models/commod"
"91porn-server/models/s/dailyretentionmod"
"context"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type AdStatType int
const (
AdStatTypeUserRetained AdStatType = 1 // 用户留存数
AdStatTypeUserRates AdStatType = 2 // 用户留存率
AdStatTypeAdClickAcc AdStatType = 3 // 广告点击累计
AdStatTypePayTotalAcc AdStatType = 4 // 充值总额累计
AdStatTypePayAndAdClickTotalAcc AdStatType = 5 // 充值总额+广告点击
)
type QueryReq struct {
StartDate *time.Time `json:"startDate" form:"startDate"` // 开始日期
EndDate *time.Time `json:"endDate" form:"endDate"` // 结束日期
commod.Page
}
type AdGroupStatReq struct {
Type AdStatType `json:"type" form:"type"` // 类型 1-用户留存数 2-用户留存率 3-广告点击累计 4-充值总额累计 5-充值总额+广告点击
QueryReq
}
type QueryRes struct {
Total int64 `json:"total"` // 总数
List []Item `json:"list"` // 列表
}
type Item struct {
ID primitive.ObjectID `json:"id" bson:"id"` // ID
Date time.Time `json:"date" bson:"date"` // 日期
GroupA []string `json:"groupA" bson:"groupA"` // A组数据(0-30天)
GroupB []string `json:"groupB" bson:"groupB"` // B组数据(0-30天)
GroupC []string `json:"groupC" bson:"groupC"` // C组数据(0-30天)
}
func GetAdGroupStatList(ctx context.Context, req *AdGroupStatReq) (*QueryRes, error) {
var err error
var res *QueryRes
switch req.Type {
case AdStatTypeUserRetained:
res, err = QueryUserRetained(ctx, &req.QueryReq)
case AdStatTypeUserRates:
res, err = QueryUserRates(ctx, &req.QueryReq)
case AdStatTypeAdClickAcc:
res, err = QueryTotalClickAcc(ctx, &req.QueryReq)
case AdStatTypePayTotalAcc:
res, err = QueryPayTotalAcc(ctx, &req.QueryReq)
case AdStatTypePayAndAdClickTotalAcc:
res, err = QueryTotalPayAndClickAcc(ctx, &req.QueryReq)
default:
return nil, errors.New("未知的类型")
}
if err != nil {
return nil, err
}
//res, _ = GetAdGroupStatListMose(ctx, req) // 使用模拟数据替代真实查询
return res, nil
}
type dataExtractor func(item *dailyretentionmod.DailyRetention) []string
// queryRetention 通用查询函数
func queryRetention(ctx context.Context, req *QueryReq, extractor dataExtractor) (*QueryRes, error) {
startDate := time.Time{}
endDate := time.Now()
if req.StartDate != nil {
startDate = time.Date(req.StartDate.Year(), req.StartDate.Month(), req.StartDate.Day(), 0, 0, 0, 0, req.StartDate.Location())
}
if req.EndDate != nil {
endDate = time.Date(req.EndDate.Year(), req.EndDate.Month(), req.EndDate.Day(), 0, 0, 0, 0, req.EndDate.Location())
}
filter := bson.M{
"date": bson.M{
"$gte": startDate,
"$lte": endDate,
},
}
// 查询总数(按日期去重)
total, err := countDistinctDates(ctx, filter)
if err != nil {
return nil, fmt.Errorf("count failed: %w", err)
}
// 分页查询
skip := int64((req.PageNumber - 1) * req.PageSize)
limit := int64(req.PageSize)
// 先查询日期范围内的所有记录
opts := options.Find().SetSort(bson.D{{Key: "date", Value: -1}})
items, err := dailyretentionmod.QueryAllList(filter, opts)
if err != nil {
return nil, fmt.Errorf("query failed: %w", err)
}
// 按日期分组
dateMap := make(map[time.Time]map[commod.AdGroup]*dailyretentionmod.DailyRetention)
for _, item := range items {
if dateMap[item.Date] == nil {
dateMap[item.Date] = make(map[commod.AdGroup]*dailyretentionmod.DailyRetention)
}
dateMap[item.Date][item.AdGroup] = item
}
// 收集所有日期并排序
dates := make([]time.Time, 0, len(dateMap))
for date := range dateMap {
dates = append(dates, date)
}
sortDatesDesc(dates)
// 分页处理
start := int(skip)
end := start + int(limit)
if start > len(dates) {
start = len(dates)
}
if end > len(dates) {
end = len(dates)
}
pagedDates := dates[start:end]
// 构建结果
list := make([]Item, 0, len(pagedDates))
for _, date := range pagedDates {
groupMap := dateMap[date]
item := Item{
Date: common.NormalizeDate(date),
GroupA: extractGroupData(groupMap[commod.AdGroupA], extractor),
GroupB: extractGroupData(groupMap[commod.AdGroupB], extractor),
GroupC: extractGroupData(groupMap[commod.AdGroupC], extractor),
}
list = append(list, item)
}
return &QueryRes{
Total: total,
List: list,
}, nil
}
// countDistinctDates 统计日期数量
func countDistinctDates(ctx context.Context, filter bson.M) (int64, error) {
dates, err := dailyretentionmod.Distinct(filter, "date")
if err != nil {
return 0, err
}
return int64(len(dates)), nil
}
// sortDatesDesc 日期降序排序
func sortDatesDesc(dates []time.Time) {
for i := 0; i < len(dates)-1; i++ {
for j := i + 1; j < len(dates); j++ {
if dates[i].Before(dates[j]) {
dates[i], dates[j] = dates[j], dates[i]
}
}
}
}
// extractGroupData 提取单个广告组的数据
func extractGroupData(item *dailyretentionmod.DailyRetention, extractor dataExtractor) []string {
if item == nil {
// 返回 31 个空字符串
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := range result {
result[i] = ""
}
return result
}
return extractor(item)
}
// QueryUserRetained 查询用户留存人数
func QueryUserRetained(ctx context.Context, req *QueryReq) (*QueryRes, error) {
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
if item.UserRetained != nil {
result[i] = fmt.Sprintf("%d", item.UserRetained[i])
} else {
result[i] = "0"
}
}
return result
})
}
// QueryUserRates 查询用户留存率
func QueryUserRates(ctx context.Context, req *QueryReq) (*QueryRes, error) {
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
if item.UserRates != nil {
result[i] = formatRatePercentage(item.UserRates[i])
} else {
result[i] = "100%"
}
}
return result
})
}
// QueryPayTotalAcc 查询充值累计金额
func QueryPayTotalAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
if item.PayTotalAcc != nil {
result[i] = fmt.Sprintf("%d", item.PayTotalAcc[i]/100) // 金额以分为单位,转换为元
} else {
result[i] = "0"
}
}
return result
})
}
// QueryTotalClickAcc 查询点击累计次数
func QueryTotalClickAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
if item.TotalClickAcc != nil {
result[i] = fmt.Sprintf("%d", item.TotalClickAcc[i])
} else {
result[i] = "0"
}
}
return result
})
}
// QueryTotalPayAndClickAcc 查询点击累计次数
func QueryTotalPayAndClickAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
if item.TotalClickAcc != nil {
result[i] = fmt.Sprintf("%d", item.TotalClickAcc[i]+item.PayTotalAcc[i]/100)
} else {
result[i] = "0"
}
}
return result
})
}
func formatRatePercentage(rate float64) string {
return fmt.Sprintf("%.2f%%", rate*100)
}
@@ -0,0 +1,21 @@
package advgroupser
import "testing"
func TestFormatRatePercentage(t *testing.T) {
t.Run("formats decimal rate as percentage", func(t *testing.T) {
got := formatRatePercentage(0.1234)
want := "12.340%"
if got != want {
t.Fatalf("expected %s, got %s", want, got)
}
})
t.Run("formats zero as zero percentage", func(t *testing.T) {
got := formatRatePercentage(0)
want := "0.000%"
if got != want {
t.Fatalf("expected %s, got %s", want, got)
}
})
}
@@ -0,0 +1,468 @@
package ai_changeface_img_ser
import (
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/httputil"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/aichangefaceimgmod"
"91porn-server/models/v/aichangefacevidmod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/proto"
"91porn-server/web/webg"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func List(req *aichangefaceimgmod.WebListRequest) (interface{}, stderr.Code) {
var data = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := aichangefaceimgmod.CountDocument(req.Filter())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := aichangefaceimgmod.QueryAllDocument(req.Filter(), req.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func Update(act string, req *aichangefaceimgmod.EditCond) stderr.Code {
// 获取Ai记录
data, err := aichangefaceimgmod.FindDataByID(req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return stderr.CodeEmptyData
}
if (*req.Status == aichangefaceimgmod.FAILURE || *req.Status == aichangefaceimgmod.REFUND) &&
(data.Status == aichangefaceimgmod.FAILURE || data.Status == aichangefaceimgmod.REFUND) {
return stderr.Success
}
// 根据状态判断
switch *req.Status {
case aichangefaceimgmod.Processing:
if data.Status != aichangefaceimgmod.SUCCESS {
return stderr.AiChangeFaceImgStatusIsErr
}
err = aichangefaceimgmod.Edit(nil, req.Filter(), req.Update(act))
if err != nil {
return stderr.ErrDbUpdateError
}
case aichangefaceimgmod.SUCCESS:
if err = aichangefaceimgmod.Edit(nil, req.Filter(), req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefaceimgmod.FAILURE, aichangefaceimgmod.REFUND:
// 失败处理 退金币
deAmt := -data.DebitAmountCoin
deIncome := -data.DebitIncomeCoin
uid := data.UID
if err = webg.VideoDB.Trans(func(tool *db.MongoTool) error {
updateSet, _ := req.Update(act)["$set"].(bson.M)
transitioned, transitionErr := aichangefaceimgmod.TransitionStatus(tool, data.ID, data.Status, *req.Status, updateSet)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
var tl []txnmod.TransactionLog
// 根据是否是免费次数退回
if data.Count > 0 || data.IsFreeTimes {
deCount := data.Count
if data.Count == 0 {
deCount = 1
}
// 加钱包余额
wallet, wErr := walletmod.DebitAiFreeTimes(tool, -deCount, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAiFreeTimes error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: deCount,
ActualAmount: float64(deCount),
TranType: txnmod.AiChangeFaceImgDebitFreeTimesReturn.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgDebitFreeTimesReturn),
Desc: fmt.Sprintf("AI图片换脸免费次数退返[%d次]", deCount),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 || data.DebitIncomeCoin > 0 {
// 加钱包余额
wallet, wErr := walletmod.ReturnAmountAndIncome(tool, deAmt, deIncome, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deIncome,
ActualAmount: float64(-deIncome),
TranType: txnmod.AiChangeFaceImgReturnIncomeGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnIncomeGold),
Desc: fmt.Sprintf("AI图片换脸收益金币退返-%d", -deIncome),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deAmt,
ActualAmount: float64(-deAmt),
TranType: txnmod.AiChangeFaceImgReturnGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnGold),
Desc: fmt.Sprintf("AI图片换脸金币退返-%d", -deAmt),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit Trans error:%+v;uid:%v;", err, uid))
return stderr.ErrDbUpdateError
}
}
return stderr.Success
}
func UpdateNew(act string, req *aichangefaceimgmod.EditCond) stderr.Code {
// 获取Ai记录
data, err := aichangefaceimgmod.FindDataByID(req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return stderr.CodeEmptyData
}
if *req.Status == aichangefaceimgmod.StatusSubmit && data.Status == aichangefaceimgmod.StatusSubmit {
return stderr.Success
}
if (*req.Status == aichangefaceimgmod.FAILURE || *req.Status == aichangefaceimgmod.REFUND) &&
(data.Status == aichangefaceimgmod.FAILURE || data.Status == aichangefaceimgmod.REFUND) {
return stderr.Success
}
// 根据状态判断
switch *req.Status {
case aichangefaceimgmod.Processing:
err = aichangefaceimgmod.Edit(nil, req.Filter(), req.Update(act))
if err != nil {
return stderr.ErrDbUpdateError
}
case aichangefaceimgmod.StatusSubmit:
//提交订单到第三方
notifyURL, notifyErr := webg.AICallbackURL("/api/web/admin/ai/change_face_img/callback")
if notifyErr != nil {
log.Error("AI图片换脸回调地址配置异常", log.E(notifyErr))
return stderr.Failure
}
httpReq := proto.AiChangeFaceReq{
AppId: int(commod.KFK_APPID),
OrderNumAPP: data.ID.Hex(),
Title: data.ModTitle,
SourceImageUrl: data.OriginPic,
NotifyUrl: notifyURL,
}
url := webg.Conf.URL.AiServer + "/api/changeface/prd/changeFaceByVideo"
//第三方请求地址
var resp proto.AiChangeFaceResp
codeJ, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, httpReq)
if err != nil {
log.Error("CreateChangeFace httputil.DefaultClientPostJsonWithResp err", log.E(err))
return stderr.Failure
}
if codeJ != 200 {
log.Error("CreateChangeFace req http code error", log.Any("code", codeJ))
return stderr.Failure
}
if resp.Code == 40000 {
return stderr.ChangeFaceNoModule
}
if err = aichangefaceimgmod.Edit(nil, req.Filter(), req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefaceimgmod.FAILURE, aichangefaceimgmod.REFUND:
// 失败处理 退金币
deAmt := -data.DebitAmountCoin
deIncome := -data.DebitIncomeCoin
uid := data.UID
if err = webg.VideoDB.Trans(func(tool *db.MongoTool) error {
updateSet, _ := req.Update(act)["$set"].(bson.M)
transitioned, transitionErr := aichangefaceimgmod.TransitionStatus(tool, data.ID, data.Status, *req.Status, updateSet)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
var tl []txnmod.TransactionLog
// 根据是否是免费次数退回
if data.Count > 0 || data.IsFreeTimes {
deCount := data.Count
if data.Count == 0 {
deCount = 1
}
// 加钱包余额
wallet, wErr := walletmod.DebitAiFreeTimes(tool, -deCount, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAiFreeTimes error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: deCount,
ActualAmount: float64(deCount),
TranType: txnmod.AiChangeFaceImgDebitFreeTimesReturn.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgDebitFreeTimesReturn),
Desc: fmt.Sprintf("AI图片换脸免费次数退返[%d次]", deCount),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 || data.DebitIncomeCoin > 0 {
// 加钱包余额
wallet, wErr := walletmod.ReturnAmountAndIncome(tool, deAmt, deIncome, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deIncome,
ActualAmount: float64(-deIncome),
TranType: txnmod.AiChangeFaceImgReturnIncomeGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnIncomeGold),
Desc: fmt.Sprintf("AI图片换脸收益金币退返-%d", -deIncome),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deAmt,
ActualAmount: float64(-deAmt),
TranType: txnmod.AiChangeFaceImgReturnGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnGold),
Desc: fmt.Sprintf("AI图片换脸金币退返-%d", -deAmt),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit Trans error:%+v;uid:%v;", err, uid))
return stderr.ErrDbUpdateError
}
}
return stderr.Success
}
func CallBackOrder(req *aichangefacevidmod.CallbackReq) stderr.Code {
//根据订单号更新ai换脸订单
id, _ := primitive.ObjectIDFromHex(req.AppOrderNum)
//查询该笔订单是否存在
aichangeFaceInfo, err := aichangefaceimgmod.FindByID(nil, id)
if aichangeFaceInfo == nil || err != nil {
return stderr.ErrDbQueryError
}
oldOrderTime := time.Date(2025, 9, 15, 0, 0, 0, 0, time.Local)
if aichangeFaceInfo.CreatedAt.Before(oldOrderTime) &&
(aichangeFaceInfo.Status == aichangefaceimgmod.SUCCESS || aichangeFaceInfo.Status == aichangefaceimgmod.FAILURE || aichangeFaceInfo.Status == aichangefaceimgmod.REFUND) {
// 以前的单子如果处理过了就新下资源和状态
set := bson.M{"newPic": []string{strings.TrimSpace(req.ImgUrl)}}
if req.Msg == "操作成功" {
set["status"] = aichangefaceimgmod.SUCCESS
} else {
set["remark"] = req.Msg
set["status"] = aichangefaceimgmod.REFUND
}
_ = aichangefaceimgmod.Update(nil, id, set)
return stderr.Success
}
if aichangeFaceInfo.Status != aichangefaceimgmod.StatusSubmit {
log.Error("AI图片换脸-重复回调或者状态不正确", log.Any("id", aichangeFaceInfo.ID.Hex()), log.Any("status", aichangeFaceInfo.Status))
return stderr.Success
}
var status = aichangefaceimgmod.SUCCESS
//处理金币退款以及后续操作
set := bson.M{}
if req.ImgUrl != "" {
set["newPic"] = append([]string{}, strings.TrimSpace(req.ImgUrl))
}
if req.Msg != "操作成功" {
set["remark"] = req.Msg
status = aichangefaceimgmod.REFUND
}
set["status"] = status
switch status {
case aichangefaceimgmod.SUCCESS:
transitioned, updateErr := aichangefaceimgmod.TransitionStatus(nil, id, aichangefaceimgmod.StatusSubmit, aichangefaceimgmod.SUCCESS, set)
if updateErr != nil {
return stderr.ErrDbUpdateError
}
if !transitioned {
return stderr.Success
}
common.Go(func() {
if aichangeFaceInfo.ShareStatus != 1 {
return
}
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 1,
OrderId: aichangeFaceInfo.ID,
Template: aichangeFaceInfo.MId,
Uid: aichangeFaceInfo.UID,
Title: aichangeFaceInfo.ShareTitle,
OriginalImage: aichangeFaceInfo.ModPic,
OriginalVideo: "",
OriginContent: "",
GenerateImage: req.ImgUrl,
GenerateVideo: "",
Status: aiplazamod.DefaultStatus,
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
return stderr.Success
case aichangefaceimgmod.REFUND:
//换脸失败,进行退款操作
// 失败处理 退金币
deAmt := -aichangeFaceInfo.DebitAmountCoin
deIncome := -aichangeFaceInfo.DebitIncomeCoin
uid := aichangeFaceInfo.UID
if err = webg.VideoDB.Trans(func(tool *db.MongoTool) error {
transitioned, transitionErr := aichangefaceimgmod.TransitionStatus(tool, id, aichangefaceimgmod.StatusSubmit, aichangefaceimgmod.REFUND, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
var tl []txnmod.TransactionLog
// 根据是否是免费次数退回
if aichangeFaceInfo.Count > 0 || aichangeFaceInfo.IsFreeTimes {
deCount := aichangeFaceInfo.Count
if aichangeFaceInfo.Count == 0 {
deCount = 1
}
// 加钱包余额
wallet, wErr := walletmod.DebitAiFreeTimes(tool, -deCount, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAiFreeTimes error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: deCount,
ActualAmount: float64(deCount),
TranType: txnmod.AiChangeFaceImgDebitFreeTimesReturn.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgDebitFreeTimesReturn),
Desc: fmt.Sprintf("AI图片换脸免费次数退返[%d次]", deCount),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if aichangeFaceInfo.DebitAmountCoin > 0 || aichangeFaceInfo.DebitIncomeCoin > 0 {
// 加钱包余额
wallet, wErr := walletmod.ReturnAmountAndIncome(tool, deAmt, deIncome, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
if aichangeFaceInfo.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deIncome,
ActualAmount: float64(-deIncome),
TranType: txnmod.AiChangeFaceImgReturnIncomeGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnIncomeGold),
Desc: fmt.Sprintf("AI图片换脸收益金币退返-%d", -deIncome),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if aichangeFaceInfo.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deAmt,
ActualAmount: float64(-deAmt),
TranType: txnmod.AiChangeFaceImgReturnGold.Key(),
TranTypeInt: int64(txnmod.AiChangeFaceImgReturnGold),
Desc: fmt.Sprintf("AI图片换脸金币退返-%d", -deAmt),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeFaceImg Edit Trans error:%+v;uid:%v;", err, uid))
return stderr.ErrDbUpdateError
}
}
return stderr.Success
}
@@ -0,0 +1,540 @@
package ai_changeface_ser
import (
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/httputil"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/aichangefacemod"
"91porn-server/models/v/aichangefacevidmod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/backpackmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/proto"
"91porn-server/web/webg"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func List(id *primitive.ObjectID, req *aichangefacemod.ListRequest) ([]aichangefacemod.AiChangeFaceWebResp, int64, bool, error) {
limit := req.Limit()
acfs, count, err := aichangefacemod.WebList(id, req.Uid, req.Status, req.VidID, int(req.Skip()), int(limit+1))
if err != nil {
return nil, 0, false, err
}
hasNext := false
if len(acfs) > int(limit) {
acfs = acfs[:limit]
hasNext = true
}
acfsLen := len(acfs)
acfwrs := make([]aichangefacemod.AiChangeFaceWebResp, acfsLen)
for i, v := range acfs {
acfwrs[i] = aichangefacemod.AiChangeFaceWebResp{
ID: v.ID,
Uid: v.Uid,
Pic: v.Pic,
ModId: v.VidID,
ModUrl: v.ModVideo,
ModTitle: v.ModTitle,
Coin: v.Coin,
IncomeCoin: v.IncomeCoin,
Status: v.Status,
Cover: v.Cover,
Url: v.Url,
Remark: v.Remark,
Operator: v.Operator,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
}
return acfwrs, count, hasNext, nil
}
func Update(act string, req *aichangefacemod.EditAiChangeFace) stderr.Code {
// 获取Ai记录
data, err := aichangefacemod.FindByID(nil, req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
// 如果已经退款,直接返回,避免重复扣款
if *req.Status == aichangefacemod.StatusRefund && data.Status == aichangefacemod.StatusRefund {
return stderr.Success
}
switch *req.Status {
case aichangefacemod.StatusGenning: // 未完成或完成
if err = aichangefacemod.Update(nil, req.ID, req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
case aichangefacemod.StatusComplete:
if req.Url == nil || *req.Url == "" {
return stderr.ChangeFaceSuccessWithoutUrl
}
if err = aichangefacemod.Update(nil, req.ID, req.Update(act)); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
var tl []txnmod.TransactionLog
updateSet, _ := req.Update(act)["$set"].(bson.M)
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, req.ID, data.Status, aichangefacemod.StatusRefund, updateSet)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(data.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, data.Discount); err != nil {
return err
}
}
if data.Coin > 0 || data.IncomeCoin > 0 {
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.Coin, -data.IncomeCoin, data.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.Uid))
return err
}
if data.Coin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: data.Coin,
ActualAmount: float64(data.Coin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", data.Coin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.IncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: data.IncomeCoin,
ActualAmount: float64(data.IncomeCoin),
TranType: txnmod.AiChangefaceDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitIncomeGoldReturn),
Desc: fmt.Sprintf("AI换收益金币退返-%d", data.IncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Generate txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", data.Uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return stderr.Success
default:
return stderr.ErrParamError
}
}
func UpdateNew(act string, req *aichangefacemod.EditAiChangeFace) stderr.Code {
// 获取Ai记录
data, err := aichangefacemod.FindByID(nil, req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if *req.Status == aichangefacemod.StatusSubmit && data.Status == aichangefacemod.StatusSubmit {
return stderr.Success
}
if *req.Status == aichangefacemod.StatusRefund && data.Status == aichangefacemod.StatusRefund {
return stderr.Success
}
set := bson.M{}
set["status"] = req.Status
if req.Cover != nil {
set["cover"] = strings.TrimSpace(*req.Cover)
}
if req.Url != nil {
set["url"] = strings.TrimSpace(*req.Url)
}
if req.Remark != nil {
set["remark"] = *req.Remark
}
if len(set) == 0 { // 没有做任何修改
return stderr.Success
}
set["operator"] = act // 记录操作员
set["updatedAt"] = time.Now()
switch *req.Status {
case aichangefacemod.StatusGenning: // 未完成或完成
if err = aichangefacemod.Update(nil, req.ID, bson.M{"$set": set}); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefacemod.StatusSubmit:
//提交订单到第三方
notifyURL, notifyErr := webg.AICallbackURL("/api/web/admin/ai/changeface/callback")
if notifyErr != nil {
log.Error("AI视频换脸回调地址配置异常", log.E(notifyErr))
return stderr.Failure
}
httpReq := proto.AiChangeFaceReq{
AppId: int(commod.KFK_APPID),
OrderNumAPP: data.ID.Hex(),
Title: data.ModTitle,
SourceImageUrl: data.Pic[0],
NotifyUrl: notifyURL,
}
url := webg.Conf.URL.AiServer + "/api/changeface/prd/changeFaceByVideo"
//第三方请求地址
var resp proto.AiChangeFaceResp
codeJ, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, httpReq)
if err != nil {
log.Error("CreateChangeFace httputil.DefaultClientPostJsonWithResp err", log.E(err))
return stderr.Failure
}
if codeJ != 200 {
log.Error("CreateChangeFace req http code error", log.Any("code", codeJ))
return stderr.Failure
}
if resp.Code == 40000 {
return stderr.ChangeFaceNoModule
}
if err = aichangefacemod.Update(nil, req.ID, bson.M{"$set": set}); err != nil {
return stderr.ErrDbUpdateError
}
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, req.ID, data.Status, aichangefacemod.StatusRefund, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(data.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, data.Discount); err != nil {
return err
}
}
// 退款
if data.Coin == 0 && data.IncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.Coin, -data.IncomeCoin, data.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.Uid))
return err
}
// 添加退款记录
totalCoin := data.Coin + data.IncomeCoin
if err = txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.Uid,
Amount: totalCoin,
ActualAmount: float64(totalCoin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", totalCoin),
RealAmount: walletmod.GetRealAmount(wallet),
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Edit txnmod.InsertTransactionLog error:%+v:", err), log.Any("uid", data.Uid))
return err
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
default:
return stderr.ErrParamError
}
return stderr.Success
}
func AddMod(acfv aichangefacevidmod.AddAiChangeFaceVidReq) error {
now := time.Now()
return aichangefacevidmod.AddAiChangeFaceVid(aichangefacevidmod.AiChangeFaceVidMod{
Title: acfv.Title,
SourceURL: acfv.SourceURL,
Status: acfv.Status,
PlayTime: acfv.PlayTime,
Cover: acfv.Cover,
Type: acfv.Type,
NewUrl: acfv.NewUrl,
Coin: acfv.Coin,
VipCoin: acfv.VipCoin,
StyleType: acfv.StyleType,
ModuleType: acfv.ModuleType,
HotMark: acfv.HotMark,
HotValue: acfv.HotValue,
Sort: acfv.Sort,
SceneType: acfv.SceneType,
CreatedAt: now,
UpdatedAt: now,
})
}
func UpdateMod(eacfv aichangefacevidmod.EditAiChangeFaceVid) error {
set := bson.M{}
if eacfv.Title != nil {
set["title"] = *eacfv.Title
}
if eacfv.SourceURL != nil {
set["sourceURL"] = *eacfv.SourceURL
}
if eacfv.Status != nil {
set["status"] = *eacfv.Status
}
if eacfv.NewUrl != nil {
set["newUrl"] = *eacfv.NewUrl
}
if eacfv.PlayTime != nil {
set["playTime"] = *eacfv.PlayTime
}
if eacfv.Cover != nil {
set["cover"] = *eacfv.Cover
}
if eacfv.Type != nil {
set["type"] = *eacfv.Type
}
if eacfv.Coin != nil {
set["coin"] = *eacfv.Coin
}
if eacfv.VipCoin != nil {
set["vipCoin"] = *eacfv.VipCoin
}
if eacfv.ModuleType != nil {
set["moduleType"] = *eacfv.ModuleType
}
if eacfv.HotValue != nil {
set["hotValue"] = *eacfv.HotValue
}
if eacfv.HotMark != nil {
set["hotMark"] = *eacfv.HotMark
}
if eacfv.Sort != nil {
set["sort"] = *eacfv.Sort
}
if eacfv.SceneType != nil {
set["sceneType"] = *eacfv.SceneType
}
if len(set) == 0 {
return nil
}
set["updatedAt"] = time.Now()
return aichangefacevidmod.UpdateAiChangeFaceVid(eacfv.ID, bson.M{"$set": set})
}
func ModList(req *aichangefacevidmod.WebListRequest) (interface{}, stderr.Code) {
var (
data = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
err error
)
if err != nil {
return nil, stderr.ErrParamError
}
count, err := aichangefacevidmod.CountDocument(req.Filter())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := aichangefacevidmod.QueryAllDocument(req.Filter(), req.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func CallBackOrder(req *aichangefacevidmod.CallbackReq) stderr.Code {
//根据订单号更新ai换脸订单
id, _ := primitive.ObjectIDFromHex(req.AppOrderNum)
//查询该笔订单是否存在
aichangeFaceInfo, err := aichangefacemod.FindByID(nil, id)
if aichangeFaceInfo == nil || err != nil {
return stderr.ErrDbQueryError
}
oldOrderTime := time.Date(2025, 9, 15, 0, 0, 0, 0, time.Local)
if aichangeFaceInfo.CreatedAt.Before(oldOrderTime) &&
(aichangeFaceInfo.Status == aichangefacemod.StatusComplete || aichangeFaceInfo.Status == aichangefacemod.StatusRefund) {
// 以前的单子如果处理过了就新下资源和状态
set := bson.M{"url": strings.TrimSpace(req.ImgUrl)}
if req.Msg == "操作成功" {
set["status"] = aichangefacemod.StatusComplete
} else {
set["remark"] = req.Msg
set["status"] = aichangefacemod.StatusRefund
}
_ = aichangefacemod.Update(nil, id, bson.M{"$set": set})
return stderr.Success
}
if aichangeFaceInfo.Status != aichangefacemod.StatusSubmit {
log.Error("AI视频换脸-重复回调或者状态不正确", log.Any("id", aichangeFaceInfo.ID.Hex()), log.Any("status", aichangeFaceInfo.Status))
return stderr.Success
}
var status = aichangefacemod.StatusComplete
//处理金币退款以及后续操作
set := bson.M{}
if req.ImgUrl != "" {
set["url"] = strings.TrimSpace(req.ImgUrl)
}
if req.Msg != "操作成功" {
set["remark"] = req.Msg
status = aichangefacemod.StatusRefund
}
set["status"] = status
set["updatedAt"] = time.Now()
switch status {
case aichangefacemod.StatusComplete:
transitioned, updateErr := aichangefacemod.TransitionStatus(nil, id, aichangefacemod.StatusSubmit, aichangefacemod.StatusComplete, set)
if updateErr != nil {
return stderr.ErrDbUpdateError
}
if !transitioned {
return stderr.Success
}
common.Go(func() {
if aichangeFaceInfo.ShareStatus != 1 {
return
}
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 2,
OrderId: aichangeFaceInfo.ID,
Template: aichangeFaceInfo.VidID.Hex(),
Uid: aichangeFaceInfo.Uid,
Title: aichangeFaceInfo.ShareTitle,
OriginalImage: "",
OriginalVideo: aichangeFaceInfo.ModVideo,
OriginalVideoCover: aichangeFaceInfo.ModCover,
OriginContent: "",
GenerateVideo: req.ImgUrl,
GenerateVideoCover: aichangeFaceInfo.Pic[0],
Status: aiplazamod.DefaultStatus,
}
if len(aichangeFaceInfo.Pic) > 0 {
alPlaza.GenerateVideoCover = aichangeFaceInfo.Pic[0]
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
return stderr.Success
case aichangefacemod.StatusRefund: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
transitioned, transitionErr := aichangefacemod.TransitionStatus(t, id, aichangefacemod.StatusSubmit, aichangefacemod.StatusRefund, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
// 折扣券返还
if len(aichangeFaceInfo.Discount) > 0 {
if _, err = backpackmod.UnuseManyGoods(t, aichangeFaceInfo.Discount); err != nil {
return err
}
}
// 退款
if aichangeFaceInfo.Coin == 0 && aichangeFaceInfo.IncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -aichangeFaceInfo.Coin, -aichangeFaceInfo.IncomeCoin, aichangeFaceInfo.Uid)
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", aichangeFaceInfo.Uid))
return err
}
// 添加退款记录
totalCoin := aichangeFaceInfo.Coin + aichangeFaceInfo.IncomeCoin
if err = txnmod.InsertTransactionLog(t, &txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: aichangeFaceInfo.Uid,
Amount: totalCoin,
ActualAmount: float64(totalCoin),
TranType: txnmod.AiChangefaceDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn),
Desc: fmt.Sprintf("AI换脸金币退返-%d", totalCoin),
RealAmount: walletmod.GetRealAmount(wallet),
}); err != nil {
log.Error(fmt.Sprintf("Handle AiChangeface Edit txnmod.InsertTransactionLog error:%+v:", err), log.Any("uid", aichangeFaceInfo.Uid))
return err
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return stderr.Success
default:
return stderr.ErrParamError
}
}
type BatchUpdateReq struct {
IDS []string `json:"ids" bson:"ids"` // AI模版IDS
MID string `json:"mid" bson:"mid"` // 模块ID
}
func (q *BatchUpdateReq) Filter() primitive.M {
filter := bson.M{}
var ids []primitive.ObjectID
for _, id := range q.IDS {
objectID, _ := primitive.ObjectIDFromHex(id)
ids = append(ids, objectID)
}
if len(ids) > 0 {
filter["_id"] = bson.M{"$in": ids}
}
return filter
}
func (q *BatchUpdateReq) UpdateCond() primitive.M {
cond := bson.M{}
cond["mid"] = q.MID
cond["updatedAt"] = time.Now()
return cond
}
func (q *BatchUpdateReq) BatchUpdateMod() error {
return aichangefacevidmod.UpdateManyByCond(q.Filter(), bson.M{"$set": q.UpdateCond()})
}
+41
View File
@@ -0,0 +1,41 @@
package ai_changeface_ser
import "91porn-server/models/v/aiimagetovideomod"
type AllSceneListReq struct {
}
type AllSceneResp struct {
Name string `json:"name" bson:"name"` // 名称
Value int `json:"value" bson:"value"` // 值
}
func (in *AllSceneListReq) All() (data []AllSceneResp, err error) {
var ret []AllSceneResp
ret = append(ret, AllSceneResp{
Name: aiimagetovideomod.SceneTagLiftShirt.String(),
Value: int(aiimagetovideomod.SceneTagLiftShirt),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagTearClothes.String(),
Value: int(aiimagetovideomod.SceneTagTearClothes),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagCumOnFace.String(),
Value: int(aiimagetovideomod.SceneTagCumOnFace),
}, AllSceneResp{
Name: aiimagetovideomod.SceneOFace.String(),
Value: int(aiimagetovideomod.SceneOFace),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagKiss.String(),
Value: int(aiimagetovideomod.SceneTagKiss),
}, AllSceneResp{
Name: aiimagetovideomod.SceneTagSuckBreasts.String(),
Value: int(aiimagetovideomod.SceneTagSuckBreasts),
}, AllSceneResp{
Name: aiimagetovideomod.SceneRevealed.String(),
Value: int(aiimagetovideomod.SceneRevealed),
}, AllSceneResp{
Name: aiimagetovideomod.SceneLargerChest.String(),
Value: int(aiimagetovideomod.SceneLargerChest),
})
return ret, nil
}
+311
View File
@@ -0,0 +1,311 @@
package ai_image_to_video_ser
import (
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/aiimagetovideomod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/webg"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"strings"
"time"
)
type WebListReq struct {
ID *string `json:"id" form:"id"` // ID
UID *uint64 `json:"uid" form:"uid"` // 用户ID
Status *int `json:"status" form:"status"` // 状态
SceneType *int `json:"sceneType" form:"sceneType"` // 模版场景类型
commod.Page
}
func (q *WebListReq) Filter() primitive.M {
filter := bson.M{}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
filter["_id"] = id
}
if q.Status != nil {
filter["status"] = q.Status
}
if q.UID != nil {
filter["uid"] = q.UID
}
if q.SceneType != nil {
filter["sceneType"] = q.SceneType
}
return filter
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []aiimagetovideomod.AiImageToVideo `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
res.List, res.Total, res.HasNext, err = aiimagetovideomod.GetList(q.Filter(), int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebUpdateReq struct {
ID string `json:"id" binding:"required"` // ID
Status *int `json:"status"` // 状态
Remark *string `json:"remark"` // 备注
}
func (p *WebUpdateReq) UpdateCond() primitive.M {
data := make(map[string]interface{})
if p.Status != nil {
data["status"] = *p.Status
}
if p.Remark != nil {
data["remark"] = *p.Remark
}
data["updatedAt"] = time.Now()
return data
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
id, _ := primitive.ObjectIDFromHex(p.ID)
data, err := aiimagetovideomod.GetInfo(id)
if err != nil {
return err
}
if data.ID.IsZero() {
return errors.New("data is null")
}
if p.Status != nil {
if data.Status == int(aiimagetovideomod.StatusRefunded) || data.Status == int(aiimagetovideomod.StatusGenerationFailed) {
return errors.New("Please do not refund repeatedly.")
}
switch aiimagetovideomod.AiImageToVideoStatus(*p.Status) {
case aiimagetovideomod.StatusRefunded:
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aiimagetovideomod.UpdateByID(t, id, p.UpdateCond()); err != nil {
return err
}
// 退款
if data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.DebitAmountCoin, -data.DebitIncomeCoin, data.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.UID))
return err
}
var tl []txnmod.TransactionLog
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitAmountCoin,
ActualAmount: float64(-data.DebitAmountCoin),
TranType: txnmod.AiImageToVideoDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiImageToVideoDebitGoldReturn),
Desc: fmt.Sprintf("退还AI图生视频金币-%d", data.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitIncomeCoin,
ActualAmount: float64(-data.DebitIncomeCoin),
TranType: txnmod.AiImageToVideoDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiImageToVideoDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI图生视频收益金币-%d", data.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Edit txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", data.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
default:
}
}
if _, err = aiimagetovideomod.UpdateByID(nil, id, p.UpdateCond()); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := aiimagetovideomod.DeleteByID(nil, p.ID); err != nil {
return err
}
return nil
}
// CallbackReq 换脸订单回调请求
type CallbackReq struct {
ImgUrl string `json:"imgUrl"` // AI图生视频图片地址
AppOrderNum string `json:"appOrderNum"` // app订单号
Status int `json:"status"` // 订单状态 1:成功,2:失败
Msg string `json:"msg"` // 消息
}
func (p *CallbackReq) Callback() error {
id, _ := primitive.ObjectIDFromHex(p.AppOrderNum)
// 查询该笔订单是否存在
data, err := aiimagetovideomod.GetInfo(id)
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return errors.New("ai imagetovideo data is null")
}
if data.Status != int(aiimagetovideomod.StatusSubmitted) {
log.Error("AI图生视频-重复回调或者状态不正确", log.Any("id", data.ID.Hex()), log.Any("status", data.Status))
return nil
}
var status = aiimagetovideomod.StatusGenerationSuccess
// 处理金币退款以及后续操作
set := bson.M{}
if p.ImgUrl != "" {
set["newImgUrl"] = strings.TrimSpace(p.ImgUrl)
}
if p.Status != 1 {
set["remark"] = p.Msg
set["newImgUrl"] = ""
status = aiimagetovideomod.StatusGenerationFailed
}
set["status"] = status
set["updatedAt"] = time.Now()
switch status {
case aiimagetovideomod.StatusGenerationSuccess:
if _, err = aiimagetovideomod.UpdateByID(nil, id, set); err != nil {
return stderr.ErrDbUpdateError
}
if data.ShareStatus == 1 {
common.Go(func() {
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 4,
OrderId: data.ID,
Template: "",
Uid: data.UID,
Title: data.ShareTitle,
OriginalImage: data.ImgUrl,
OriginalVideo: "",
OriginContent: "",
GenerateImage: p.ImgUrl,
GenerateVideo: "",
Status: aiplazamod.DefaultStatus,
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
}
return nil
case aiimagetovideomod.StatusGenerationFailed: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aiimagetovideomod.UpdateByID(t, id, set); err != nil {
return err
}
// 退款
if data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.DebitAmountCoin, -data.DebitIncomeCoin, data.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle Callback Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.UID))
return err
}
var tl []txnmod.TransactionLog
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitAmountCoin,
ActualAmount: float64(-data.DebitAmountCoin),
TranType: txnmod.AiImageToVideoDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiImageToVideoDebitGoldReturn),
Desc: fmt.Sprintf("退还AI图生视频金币-%d", data.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitIncomeCoin,
ActualAmount: float64(-data.DebitIncomeCoin),
TranType: txnmod.AiImageToVideoDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiImageToVideoDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI图生视频收益金币-%d", data.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Callback txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", data.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return nil
default:
}
return nil
}
+45
View File
@@ -0,0 +1,45 @@
package ai_mate_ser
import (
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/txnmod"
"fmt"
"go.mongodb.org/mongo-driver/bson"
)
type AIMateTransactionInfo struct {
OrderId string `json:"orderId"` // 订单Id
Credit_delta string `json:"credit_delta"` // 此次操作积分数
Post_credit string `json:"post_credit"` // 操作后剩余积分数
Created_at string `json:"created_at"` // 操作时间
Operation_type string `json:"operation_type"` // 操作类型
}
// GetBalance 获取AI伴侣用户交易记录
func GetUserTransactionsList(uid uint64, pageNumber, pageSize uint64) (code stderr.Code, data map[string]interface{}) {
code = stderr.Success
filter := bson.M{"tranTypeInt": bson.M{"$in": []txnmod.TransType{txnmod.AiMateChat, txnmod.AdminAiMateSet, txnmod.AiMateCurrencyExchange}}, "uid": uid}
list, _, total, err := txnmod.GetCoinLogs(pageNumber, pageSize, filter)
if err != nil {
log.Error("ai_mate_ser GetCoinLogs", log.Any("err", err), log.Any("uid", uid))
return
}
rsl := make([]AIMateTransactionInfo, 0)
for _, v := range list {
info := AIMateTransactionInfo{
OrderId: v.ID.Hex(),
Credit_delta: fmt.Sprintf("%v", v.AiMatePoint),
Post_credit: fmt.Sprintf("%v", v.RealAiMatePoint),
Created_at: v.CreatedAt.String(),
Operation_type: v.TranType,
}
rsl = append(rsl, info)
}
data = map[string]interface{}{}
data["count"] = total
data["list"] = rsl
return
}
+39
View File
@@ -0,0 +1,39 @@
package ai_mate_v2_ser
import (
"context"
"strconv"
"time"
"91porn-server/common/laosiji_app"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/web/webg"
)
// GetUserOrderLogs 查询老司机AI女友V2第三方消费流水。
func GetUserOrderLogs(ctx context.Context, uid, pageNumber, pageSize uint64, startTime, endTime time.Time) (code stderr.Code, data map[string]interface{}) {
resp, err := laosiji_app.GetAiMateOrderLogs(ctx, webg.Conf.Base.Env, laosiji_app.AiMateOrderLogsReq{
AppID: commod.KFK_APPID,
UID: uid,
Page: int(pageNumber),
PageSize: int(pageSize),
StartTime: startTime,
EndTime: endTime,
})
if err != nil {
log.Error("ai_mate_v2_ser GetUserOrderLogs", log.Any("err", err), log.Any("uid", uid))
return stderr.Failure, nil
}
total, _ := strconv.ParseInt(resp.Total, 10, 64)
items := resp.Items
if items == nil {
items = make([]laosiji_app.AiMateOrderLog, 0)
}
return stderr.Success, map[string]interface{}{
"count": total,
"list": items,
}
}
+155
View File
@@ -0,0 +1,155 @@
package ai_template_module_ser
import (
"time"
"91porn-server/models/commod"
"91porn-server/models/v/aitemplatemodulemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
Type *int `json:"type" form:"type"` // 模块类型
Status *int `json:"status" form:"status"` // 状态
commod.Page
}
func (q *WebListReq) Filter() primitive.M {
filter := bson.M{}
if q.Status != nil {
filter["status"] = q.Status
}
if q.Type != nil {
filter["type"] = q.Type
}
return filter
}
type WebListRes struct {
Total int64 `json:"total"`
List []aitemplatemodulemod.AiTemplateModule `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
sort := bson.D{{"sortNum", -1}}
res.List, res.Total, _, err = aitemplatemodulemod.GetList(q.Filter(), int64(q.Skip()), int64(q.Limit()), sort)
return
}
type WebAllListReq struct {
}
func (q *WebAllListReq) Filter() primitive.M {
filter := bson.M{}
filter["status"] = 1
filter["isDelete"] = false
return filter
}
type WebAllListRes struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` // 文档id
Type int `json:"type" bson:"type"` // 模块类型 1:AI视频换脸,2:AI图片换脸
Name string `json:"name" bson:"name"` // 模块名称
}
// GetList 获取列表
func (q *WebAllListReq) GetList() (res []WebAllListRes, err error) {
var ret []WebAllListRes
sort := bson.D{{"sortNum", -1}}
data, err := aitemplatemodulemod.GetAll(q.Filter(), sort)
if err != nil {
return ret, err
}
if data != nil && len(data) > 0 {
for _, d := range data {
ret = append(ret, WebAllListRes{
ID: d.ID,
Name: d.Name,
Type: d.Type,
})
}
}
return ret, nil
}
type WebCreateReq struct {
Type int `json:"type" form:"type" binding:"required"` // 模块类型
Name string `json:"name" form:"name" binding:"required"` // 模块名称
Status int `json:"status" form:"status" binding:"required"` // 状态
SortNum int `json:"sortNum" form:"sortNum" binding:"required"` // 模块排序
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := aitemplatemodulemod.AiTemplateModule{
Type: p.Type,
Name: p.Name,
Status: p.Status,
SortNum: p.SortNum,
IsDelete: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 创建数据
if _, err := aitemplatemodulemod.Insert(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Type *int `json:"type"` // 模块类型
Name *string `json:"name"` // 模块名称
Status *int `json:"status"` // 状态
SortNum *int `json:"sortNum"` // 模块排序
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := aitemplatemodulemod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Type != nil {
data["type"] = *p.Type
}
if p.Name != nil {
data["name"] = *p.Name
}
if p.Status != nil {
data["status"] = *p.Status
}
if p.SortNum != nil {
data["sortNum"] = *p.SortNum
}
data["updatedAt"] = time.Now()
if _, err = aitemplatemodulemod.UpdateByID(nil, p.ID, data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := aitemplatemodulemod.DeleteByID(nil, p.ID); err != nil {
return err
}
return nil
}
+325
View File
@@ -0,0 +1,325 @@
package ai_text_to_image_ser
import (
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/aiimagetovideomod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/webg"
"errors"
"fmt"
"strings"
"time"
"91porn-server/models/cache/aitexttoimagedata"
"91porn-server/models/commod"
"91porn-server/models/v/aitexttoimagemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
ID *string `json:"id" form:"id"` // ID
UID *uint64 `json:"uid" form:"uid"` // 用户ID
Status *int `json:"status" form:"status"` // 状态
commod.Page
}
func (q *WebListReq) Filter() primitive.M {
filter := bson.M{}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
filter["_id"] = id
}
if q.Status != nil {
filter["status"] = q.Status
}
if q.UID != nil {
filter["uid"] = q.UID
}
return filter
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []aitexttoimagemod.AiTextToImage `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
res.List, res.Total, res.HasNext, err = aitexttoimagemod.GetList(q.Filter(), int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebUpdateReq struct {
ID string `json:"id" binding:"required"` // ID
Status *int `json:"status"` // 状态
Remark *string `json:"remark"` // 备注
}
func (p *WebUpdateReq) UpdateCond() primitive.M {
data := make(map[string]interface{})
if p.Status != nil {
data["status"] = *p.Status
}
if p.Remark != nil {
data["remark"] = *p.Remark
}
data["updatedAt"] = time.Now()
return data
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
id, _ := primitive.ObjectIDFromHex(p.ID)
data, err := aitexttoimagemod.GetInfo(id)
if err != nil {
return err
}
if data.ID.IsZero() {
return errors.New("data is null")
}
if p.Status != nil {
if data.Status == int(aitexttoimagemod.StatusRefunded) || data.Status == int(aiimagetovideomod.StatusGenerationFailed) {
return errors.New("Please do not refund repeatedly.")
}
switch aitexttoimagemod.AiTextToImageStatus(*p.Status) {
case aitexttoimagemod.StatusRefunded:
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aitexttoimagemod.UpdateByID(t, id, p.UpdateCond()); err != nil {
return err
}
// 退款
if data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.DebitAmountCoin, -data.DebitIncomeCoin, data.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.UID))
return err
}
var tl []txnmod.TransactionLog
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitAmountCoin,
ActualAmount: float64(-data.DebitAmountCoin),
TranType: txnmod.AiTextToImageDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitGoldReturn),
Desc: fmt.Sprintf("退还AI绘图金币-%d", data.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitIncomeCoin,
ActualAmount: float64(-data.DebitIncomeCoin),
TranType: txnmod.AiTextToImageDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI绘图收益金币-%d", data.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Edit txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", data.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
default:
}
}
if _, err = aitexttoimagemod.UpdateByID(nil, id, p.UpdateCond()); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
data, err := aitexttoimagemod.GetInfo(p.ID)
if err != nil {
return err
}
if err = validateDeleteStatus(data.Status); err != nil {
return err
}
if err := aitexttoimagedata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
func validateDeleteStatus(status int) error {
if status == int(aitexttoimagemod.StatusOrderSuccess) ||
status == int(aitexttoimagemod.StatusSubmitted) {
return stderr.AiGenningDelForbidden
}
return nil
}
// CallbackReq 换脸订单回调请求
type CallbackReq struct {
ImgUrl string `json:"imgUrl"` // AI图生视频图片地址
AppOrderNum string `json:"appOrderNum"` // app订单号
Status int `json:"status"` // 订单状态 1:成功,2:失败
Msg string `json:"msg"` // 消息
}
func (p *CallbackReq) Callback() error {
id, _ := primitive.ObjectIDFromHex(p.AppOrderNum)
// 查询该笔订单是否存在
data, err := aitexttoimagemod.GetInfo(id)
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return errors.New("ai imagetovideo data is null")
}
if data.Status != int(aitexttoimagemod.StatusSubmitted) {
log.Error("AI文生图-重复回调或者状态不正确", log.Any("id", data.ID.Hex()), log.Any("status", data.Status))
return nil
}
var status = aitexttoimagemod.StatusGenerationSuccess
// 处理金币退款以及后续操作
set := bson.M{}
if p.ImgUrl != "" {
set["newImgUrl"] = strings.TrimSpace(p.ImgUrl)
}
if p.Status != 1 {
set["remark"] = p.Msg
set["newImgUrl"] = ""
status = aitexttoimagemod.StatusGenerationFailed
}
set["status"] = status
set["updatedAt"] = time.Now()
switch status {
case aitexttoimagemod.StatusGenerationSuccess:
if _, err = aitexttoimagemod.UpdateByID(nil, id, set); err != nil {
return stderr.ErrDbUpdateError
}
common.Go(func() {
if data.ShareStatus != 1 {
return
}
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 5,
OrderId: data.ID,
Template: "",
Uid: data.UID,
Title: data.ShareTitle,
OriginalImage: "",
OriginalVideo: "",
OriginContent: data.Text,
GenerateImage: p.ImgUrl,
GenerateVideo: "",
Status: aiplazamod.DefaultStatus,
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
return nil
case aitexttoimagemod.StatusGenerationFailed: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aitexttoimagemod.UpdateByID(t, id, set); err != nil {
return err
}
// 退款
if data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.DebitAmountCoin, -data.DebitIncomeCoin, data.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle Callback Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.UID))
return err
}
var tl []txnmod.TransactionLog
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitAmountCoin,
ActualAmount: float64(-data.DebitAmountCoin),
TranType: txnmod.AiTextToImageDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitGoldReturn),
Desc: fmt.Sprintf("退还AI绘图金币-%d", data.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: -data.DebitIncomeCoin,
ActualAmount: float64(-data.DebitIncomeCoin),
TranType: txnmod.AiTextToImageDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI绘图收益金币-%d", data.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle ai text to image Callback txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", data.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return nil
default:
}
return nil
}
+342
View File
@@ -0,0 +1,342 @@
package ai_text_to_novel_ser
import (
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/cache/aitexttonoveldata"
"91porn-server/models/commod"
"91porn-server/models/v/aitexttonovelmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/webg"
"errors"
"fmt"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
ID *string `json:"id" form:"id"` // ID
UID *uint64 `json:"uid" form:"uid"` // 用户ID
Status *int `json:"status" form:"status"` // 状态
commod.Page
}
func (q *WebListReq) Filter() primitive.M {
filter := bson.M{}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
filter["_id"] = id
}
if q.Status != nil {
filter["status"] = q.Status
}
if q.UID != nil {
filter["uid"] = q.UID
}
return filter
}
func (q *WebListReq) Sort() primitive.D {
return bson.D{{Key: "createdAt", Value: -1}}
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []aitexttonovelmod.AiTextToNovel `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
res.List, res.Total, res.HasNext, err = aitexttonovelmod.GetList(q.Filter(), int64(q.Skip()), int64(q.Limit()), q.Sort())
return
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
UID *uint64 `json:"uid"` // 用户ID
Background *string `json:"background"` // 小说人物设定/故事背景等
Scene *string `json:"scene"` // 地点场景
Text *string `json:"text"` // 故事情节描述
Other *string `json:"other"` // 小说其他要求
ModelType *string `json:"modelType"` // AI小说模型 12
Content *string `json:"content"` // AI生成小说正文
Status *int `json:"status"` // 状态
Coin *int64 `json:"coin"` // AI小说金币个数
DebitAmountCoin *int64 `json:"debitAmountCoin"` // 此次AI小说扣除金币个数
DebitIncomeCoin *int64 `json:"debitIncomeCoin"` // 此次AI小说扣除收益金币个数
IsFreeTimes *bool `json:"isFreeTimes"` // 是否使用免费次数
IsHide *bool `json:"isHide"` // 是否被用户隐藏
Remark *string `json:"remark"` // 备注
IsDelete *bool `json:"isDelete"` // 是否删除
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
info, err := aitexttonovelmod.GetInfo(p.ID)
if err != nil {
return err
}
if info.ID.IsZero() {
return errors.New("data is null")
}
data := make(map[string]interface{})
if p.UID != nil {
data["uid"] = *p.UID
}
if p.Background != nil {
data["background"] = *p.Background
}
if p.Scene != nil {
data["scene"] = *p.Scene
}
if p.Text != nil {
data["text"] = *p.Text
}
if p.Other != nil {
data["other"] = *p.Other
}
if p.ModelType != nil {
data["modelType"] = *p.ModelType
}
if p.Content != nil {
data["content"] = *p.Content
}
if p.Status != nil {
data["status"] = *p.Status
}
if p.Coin != nil {
data["coin"] = *p.Coin
}
if p.DebitAmountCoin != nil {
data["debitAmountCoin"] = *p.DebitAmountCoin
}
if p.DebitIncomeCoin != nil {
data["debitIncomeCoin"] = *p.DebitIncomeCoin
}
if p.IsFreeTimes != nil {
data["isFreeTimes"] = *p.IsFreeTimes
}
if p.IsHide != nil {
data["isHide"] = *p.IsHide
}
if p.Remark != nil {
data["remark"] = *p.Remark
}
if p.IsDelete != nil {
data["isDelete"] = *p.IsDelete
}
data["updatedAt"] = time.Now()
if p.Status != nil && *p.Status == 5 {
if info.Status == int(aitexttonovelmod.StatusRefunded) || info.Status == int(aitexttonovelmod.StatusGenerationFailed) {
return errors.New("Please do not refund repeatedly.")
}
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aitexttonovelmod.UpdateByID(t, info.ID, data); err != nil {
return err
}
// 退款
if info.DebitAmountCoin == 0 && info.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -info.DebitAmountCoin, -info.DebitIncomeCoin, info.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle text to novel Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", info.UID))
return err
}
var tl []txnmod.TransactionLog
if info.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: info.UID,
Amount: info.DebitAmountCoin,
ActualAmount: float64(info.DebitAmountCoin),
TranType: txnmod.AiTextToNovelDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToNovelDebitGoldReturn),
Desc: fmt.Sprintf("退还AI小说金币-%d", info.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if info.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: info.UID,
Amount: info.DebitIncomeCoin,
ActualAmount: float64(info.DebitIncomeCoin),
TranType: txnmod.AiTextToNovelDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToNovelDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI小说收益金币-%d", info.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle ai text to novel txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", info.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return nil
} else {
if _, err = aitexttonoveldata.UpdateData(nil, p.ID.Hex(), data); err != nil {
}
return err
}
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
data, err := aitexttonovelmod.GetInfo(p.ID)
if err != nil {
return err
}
if err = validateDeleteStatus(data.Status); err != nil {
return err
}
if err := aitexttonoveldata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
func validateDeleteStatus(status int) error {
if status == int(aitexttonovelmod.StatusOrderSuccess) ||
status == int(aitexttonovelmod.StatusSubmitted) {
return stderr.AiGenningDelForbidden
}
return nil
}
// CallbackReq 换脸订单回调请求
type CallbackReq struct {
Content string `json:"content"` // AI小说内容
AppOrderNum string `json:"appOrderNum"` // app订单号
Status int `json:"status"` // 订单状态 1:成功,2:失败
Msg string `json:"msg"` // 消息
}
func (p *CallbackReq) Callback() error {
id, _ := primitive.ObjectIDFromHex(p.AppOrderNum)
// 查询该笔订单是否存在
data, err := aitexttonovelmod.GetInfo(id)
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return errors.New("ai text to novel data is null")
}
if data.Status != int(aitexttonovelmod.StatusSubmitted) {
log.Error("AI小说-重复回调或者状态不正确", log.Any("id", data.ID.Hex()), log.Any("status", data.Status))
return nil
}
var status = aitexttonovelmod.StatusGenerationSuccess
// 处理金币退款以及后续操作
set := bson.M{}
if p.Content != "" {
set["content"] = strings.TrimSpace(p.Content)
}
if p.Status != 1 {
set["remark"] = p.Msg
set["content"] = ""
status = aitexttonovelmod.StatusGenerationFailed
}
set["status"] = status
set["updatedAt"] = time.Now()
switch status {
case aitexttonovelmod.StatusGenerationSuccess:
if _, err = aitexttonovelmod.UpdateByID(nil, id, set); err != nil {
return stderr.ErrDbUpdateError
}
return nil
case aitexttonovelmod.StatusGenerationFailed: // 退款
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, err = aitexttonovelmod.UpdateByID(t, id, set); err != nil {
return err
}
// 退款
if data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 { // 总金额为0则不需要退款
return nil
}
// 退款进入钱包余额
wallet, err := walletmod.ReturnAmountAndIncome(t, -data.DebitAmountCoin, -data.DebitIncomeCoin, data.UID)
if err != nil {
log.Error(fmt.Sprintf("Handle Callback Edit walletmod.DebitAmount error:%+v:", err), log.Any("uid", data.UID))
return err
}
var tl []txnmod.TransactionLog
if data.DebitAmountCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: data.DebitAmountCoin,
ActualAmount: float64(data.DebitAmountCoin),
TranType: txnmod.AiTextToNovelDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToNovelDebitGoldReturn),
Desc: fmt.Sprintf("退还AI小说金币-%d", data.DebitAmountCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: data.UID,
Amount: data.DebitIncomeCoin,
ActualAmount: float64(data.DebitIncomeCoin),
TranType: txnmod.AiTextToNovelDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiTextToNovelDebitIncomeGoldReturn),
Desc: fmt.Sprintf("退还AI小说收益金币-%d", data.DebitIncomeCoin),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(t, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle ai text to image Callback txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", data.UID))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
if e, ok := err.(stderr.Code); ok {
return e
}
return stderr.ErrDbQueryError
}
return nil
default:
}
return nil
}
@@ -0,0 +1,362 @@
package ai_undress_service
import (
"91porn-server/common"
"91porn-server/models/commod"
"91porn-server/models/v/aiplazamod"
"91porn-server/models/v/walletmod"
"fmt"
"strconv"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"91porn-server/common/db"
"91porn-server/common/httputil"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/aiUnDressmod"
"91porn-server/models/v/txnmod"
"91porn-server/web/webg"
)
func List(req *aiUnDressmod.WebListRequest) (interface{}, stderr.Code) {
var data = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := aiUnDressmod.CountDocument(req.Filter())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := aiUnDressmod.QueryAllDocument(req.Filter(), req.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func Update(act string, req *aiUnDressmod.EditCond) stderr.Code {
// 获取Ai记录
data, err := aiUnDressmod.FindDataByID(req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if (*req.Status == aiUnDressmod.FAILURE || *req.Status == aiUnDressmod.REFUND) &&
(data.Status == aiUnDressmod.FAILURE || data.Status == aiUnDressmod.REFUND) {
return stderr.Success
}
// 根据状态判断
switch *req.Status {
case aiUnDressmod.Processing:
if data.Status != aiUnDressmod.SUCCESS && data.Status != aiUnDressmod.SubmitOrder {
return stderr.AiUnDressStatusIsErr
}
err = aiUnDressmod.Edit(nil, req.Filter(), req.Update(act))
if err != nil {
return stderr.ErrDbUpdateError
}
case aiUnDressmod.SUCCESS:
err = aiUnDressmod.Edit(nil, req.Filter(), req.Update(act))
if err != nil {
return stderr.ErrDbUpdateError
}
case aiUnDressmod.FAILURE, aiUnDressmod.REFUND:
// 失败处理 退金币
deAmt := -data.DebitAmountCoin
deIncome := -data.DebitIncomeCoin
uid := data.UID
oldVersion := false
err = webg.VideoDB.Trans(func(tool *db.MongoTool) error {
updateSet, _ := req.Update(act)["$set"].(bson.M)
transitioned, transitionErr := aiUnDressmod.TransitionStatus(tool, data.ID, data.Status, *req.Status, updateSet)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
var tl []txnmod.TransactionLog
// 根据是否是免费次数退回
if data.Count > 0 || data.IsFreeTimes {
deCount := data.Count
if data.Count == 0 {
deCount = 1
}
// 加钱包余额
wallet, wErr := walletmod.DebitAiFreeTimes(tool, -deCount, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAiFreeTimes error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: deCount,
ActualAmount: float64(deCount),
TranType: txnmod.AiUndressDebitFreeTimesReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitFreeTimesReturn),
Desc: fmt.Sprintf("AI脱衣免费次数退返[%d次]", deCount),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if !data.IsFreeTimes && data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 {
deAmt = -data.Coin
oldVersion = true
}
if data.DebitAmountCoin > 0 || data.DebitIncomeCoin > 0 || oldVersion {
// 加钱包余额
wallet, wErr := walletmod.ReturnAmountAndIncome(tool, deAmt, deIncome, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deIncome,
ActualAmount: float64(-deIncome),
TranType: txnmod.AiUndressDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitIncomeGoldReturn),
Desc: fmt.Sprintf("AI脱衣收益金币退返-%d", -deIncome),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 || oldVersion {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deAmt,
ActualAmount: float64(-deAmt),
TranType: txnmod.AiUndressDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitGoldReturn),
Desc: fmt.Sprintf("AI脱衣金币退返-%d", -deAmt),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
return nil
})
if err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress Edit Trans error:%+v;uid:%v;", err, uid))
return stderr.ErrDbUpdateError
}
}
return stderr.Success
}
// Auto 提交AI自动化服务
func Auto(act string, req *aiUnDressmod.AutoCond) stderr.Code {
// 获取Ai记录
data, err := aiUnDressmod.FindDataByID(req.ID)
if err != nil {
return stderr.ErrDbQueryError
}
if data.Status == aiUnDressmod.SubmitOrder {
return stderr.Success
}
//提交订单到第三方
notifyURL, notifyErr := webg.AICallbackURL("/api/web/admin/ai/undress/callback")
if notifyErr != nil {
log.Error("AI脱衣回调地址配置异常", log.E(notifyErr))
return stderr.Failure
}
httpReq := aiUnDressmod.AiUndressOrderReq{
AppId: int(commod.KFK_APPID),
FileUrl: data.OriginPics,
UserId: strconv.FormatUint(data.UID, 10),
AppOrderNum: data.ID.Hex(),
NotifyUrl: notifyURL,
}
var resp aiUnDressmod.AiUndressOrderResp
url := webg.Conf.URL.AiUndressServer + "/api/undress/prd/create_job"
codeJ, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, httpReq)
if err != nil {
log.Error("CreateUnderss httputil.DefaultClientPostWithResp err", log.E(err))
return stderr.Failure
}
if codeJ != 200 {
log.Error("CreateUnderss req http code error", log.Any("code", codeJ))
return stderr.Failure
}
//更新ai脱衣订单状态
if err := aiUnDressmod.SubmitStatus(req.ID, aiUnDressmod.SubmitOrder, "已提交"); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// CallBackOrder AI脱衣回调
func CallBackOrder(req *aiUnDressmod.CallBackReq) stderr.Code {
// 获取Ai记录
data, err := aiUnDressmod.FindDataByID(req.AppOrderNum)
if err != nil {
return stderr.ErrDbQueryError
}
if data.Status != aiUnDressmod.SubmitOrder {
log.Error("AI脱衣-重复回调或者状态不正确", log.Any("id", data.ID.Hex()), log.Any("status", data.Status))
return stderr.Success
}
var (
set = bson.M{"remark": req.Msg}
// 返还次数、返还金币、返还收益金币
refoundCount, deAmt, deIncome int64
// 订单状态
status int
oldVersion = false
successCount = len(req.ImgUrl)
uid = data.UID
_id = data.ID
isFreeTimes = false
)
if req.ImgUrl != nil && len(req.ImgUrl) > 0 {
set["newPic"] = req.ImgUrl
// 同步内存中的 data.NewPic,否则下方 goroutine 读取 data.NewPic[0] 会因未更新而越界 panic
data.NewPic = req.ImgUrl
}
if successCount > 0 { // 成功
status = aiUnDressmod.SUCCESS
refoundCount = 0
deAmt = 0
deIncome = 0
} else { // 失败
status = aiUnDressmod.REFUND
refoundCount = data.Count
// 失败处理 退金币
deAmt = -data.DebitAmountCoin
deIncome = -data.DebitIncomeCoin
isFreeTimes = data.IsFreeTimes
}
set["status"] = status
handled := false
if err := webg.VideoDB.Trans(func(tool *db.MongoTool) error {
transitioned, transitionErr := aiUnDressmod.TransitionStatus(tool, _id, aiUnDressmod.SubmitOrder, status, set)
if transitionErr != nil {
return transitionErr
}
if !transitioned {
return nil
}
handled = true
var tl []txnmod.TransactionLog
if refoundCount > 0 || isFreeTimes {
if refoundCount == 0 {
refoundCount = 1
}
// 加免费次数,负负为正
wallet, wErr := walletmod.DebitAiFreeTimes(tool, -refoundCount, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress CallBackOrder walletmod.DebitAiFreeTimes error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: refoundCount,
ActualAmount: float64(refoundCount),
TranType: txnmod.AiUndressDebitFreeTimesReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitFreeTimesReturn),
Desc: fmt.Sprintf("AI脱衣免费次数退返[%d次]", refoundCount),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if !data.IsFreeTimes && data.DebitAmountCoin == 0 && data.DebitIncomeCoin == 0 {
deAmt = -data.Coin
oldVersion = true
}
if deAmt < 0 || deIncome < 0 || oldVersion {
// 加钱包余额,负负为正
wallet, wErr := walletmod.ReturnAmountAndIncome(tool, deAmt, deIncome, uid)
if wErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress CallBackOrder walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", uid))
return stderr.ErrDbUpdateError
}
if data.DebitIncomeCoin > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deIncome,
ActualAmount: float64(-deIncome),
TranType: txnmod.AiUndressDebitIncomeGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitIncomeGoldReturn),
Desc: fmt.Sprintf("AI脱衣收益金币退返-%d", -deIncome),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
if data.DebitAmountCoin > 0 || oldVersion {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: uid,
Amount: -deAmt,
ActualAmount: float64(-deAmt),
TranType: txnmod.AiUndressDebitGoldReturn.Key(),
TranTypeInt: int64(txnmod.AiUndressDebitGoldReturn),
Desc: fmt.Sprintf("AI脱衣金币退返-%d", -deAmt),
RealAmount: walletmod.GetRealAmount(wallet),
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle AiUnDress CallBackOrder txnmod.InsertManyTransactionLog error:%+v:", txnErr), log.Any("uid", uid))
return stderr.ErrDbQueryError
}
}
return nil
}); err != nil {
log.Error(fmt.Sprintf("Handle AiUnDress CallBackOrder Trans error:%+v;uid:%v;", err, uid))
return stderr.ErrDbUpdateError
}
if !handled {
return stderr.Success
}
if successCount > 0 {
common.Go(func() {
if data.ShareStatus != 1 {
return
}
// 新增一个分享
alPlaza := aiplazamod.AiPlaza{
Type: 3,
OrderId: data.ID,
Template: "",
Uid: data.UID,
Title: data.ShareTitle,
OriginalImage: data.OriginPic,
OriginalVideo: "",
OriginContent: "",
GenerateImage: data.NewPic[0],
GenerateVideo: "",
Status: aiplazamod.DefaultStatus,
}
_, err = aiplazamod.Insert(nil, alPlaza)
if err != nil {
log.Error("新增ai广场帖子失败", log.Any("alPlaza", alPlaza), log.E(err))
return
}
})
}
return stderr.Success
}
+223
View File
@@ -0,0 +1,223 @@
package aiplazaser
import (
"strings"
"time"
"91porn-server/models/cache/aiplazadata"
"91porn-server/models/commod"
"91porn-server/models/v/aiplazamod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
OrderId *string `form:"orderId"` // 关联AI订单id
Type *int `form:"type"` // 1-ai图片换脸 2-ai视频换脸 3-ai脱衣 4-ai图生视频 5-ai绘画 6-ai小说
Status *int `form:"status"` // 0-待审核 1-审核通过 2-拒绝 3-下架
Uid *int64 `form:"uid"` // uid
Title *string `form:"title"` // 标题
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []aiplazamod.AiPlaza `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
if q.OrderId != nil {
oid, _ := primitive.ObjectIDFromHex(strings.TrimSpace(*q.OrderId))
filter["orderId"] = oid
}
if q.Status != nil {
//0-待审核 1-审核通过 2-拒绝 3-下架
filter["status"] = *q.Status
}
if q.Title != nil && len(*q.Title) > 0 {
filter["title"] = bson.M{"$regex": *q.Title, "$options": "i"}
}
if q.Type != nil {
// 1-ai图片换脸 2-ai视频换脸 3-ai脱衣 4-ai图生视频 5-ai绘画
filter["type"] = *q.Type
}
if q.Uid != nil {
filter["uid"] = *q.Uid
}
sort := bson.D{{"reviewAt", -1}}
res.List, res.Total, res.HasNext, err = aiplazamod.GetList(filter, int64(q.Skip()), int64(q.Limit()), sort)
return
}
type WebCreateReq struct {
Type int `json:"type" form:"type" binding:"required"` // 1-ai图片换脸 2-ai视频换脸 3-ai脱衣 4-ai图生视频 5-ai绘画
Template string `json:"template" form:"template"` // 关联模版id
Uid uint64 `json:"uid" form:"uid" binding:"required"` //
Title string `json:"title" form:"title"` // 标题
OriginalImage string `json:"originalImage" form:"originalImage"` // 原图
OriginalVideo string `json:"originalVideo" form:"originalVideo"` // 原视频
OriginalVideoCover string `json:"originalVideoCover" form:"originalVideoCover"` // 原视频封面
OriginContent string `json:"originContent" form:"originContent"` // 输入的原文
GenerateImage string `json:"generateImage" form:"generateImage"` // 生成的图
GenerateVideo string `json:"generateVideo" form:"generateVideo"` // 生成的视频
GenerateVideoCover string `json:"generateVideoCover" form:"generateVideoCover"` // 生成的视频封面
Status int `json:"status" form:"status"` // 0-待审核 1-审核通过 2-拒绝 3-下架
Reason string `json:"reason" form:"reason"` // 原因
SortCode int `json:"sortCode" form:"sortCode"` // 置顶排序号,大于0就是置顶
ReviewAt time.Time `json:"reviewAt" form:"reviewAt"` // 审核时间
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := aiplazamod.AiPlaza{
Type: p.Type,
Template: p.Template,
Uid: p.Uid,
Title: p.Title,
OriginalImage: p.OriginalImage,
OriginalVideo: p.OriginalVideo,
OriginContent: p.OriginContent,
GenerateImage: p.GenerateImage,
GenerateVideo: p.GenerateVideo,
GenerateVideoCover: p.GenerateVideoCover,
Status: p.Status,
Reason: p.Reason,
SortCode: p.SortCode,
ReviewAt: p.ReviewAt,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 创建数据
if _, err := aiplazadata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Type *int `json:"type"` //
Template *string `json:"template" form:"template"` // 关联模版id
Uid *uint64 `json:"uid"` //
Title *string `json:"title"` // 标题
OriginalImage *string `json:"originalImage"` // 原图
OriginalVideo *string `json:"originalVideo"` // 原视频
OriginalVideoCover *string `json:"originalVideoCover" form:"originalVideoCover"` // 原视频封面
OriginContent *string `json:"originContent"` // 输入的原文
GenerateImage *string `json:"generateImage"` // 生成的图
GenerateVideo *string `json:"generateVideo"` // 生成的视频
GenerateVideoCover *string `json:"generateVideoCover" form:"generateVideoCover"` // 生成的视频封面
Status *int `json:"status"` // 0-待审核 1-审核通过 2-拒绝 3-下架
Reason *string `json:"reason"` // 原因
SortCode *int `json:"sortCode"` // 置顶排序号,大于0就是置顶
ReviewAt *time.Time `json:"reviewAt"` // 审核时间
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
post, err := aiplazamod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Type != nil {
data["type"] = *p.Type
}
if p.Template != nil {
data["template"] = *p.Template
}
if p.Uid != nil {
data["uid"] = *p.Uid
}
if p.Title != nil {
data["title"] = *p.Title
}
if p.OriginalImage != nil {
data["originalImage"] = *p.OriginalImage
}
if p.OriginalVideo != nil {
data["originalVideo"] = *p.OriginalVideo
}
if p.OriginalVideoCover != nil {
data["originalVideoCover"] = *p.OriginalVideoCover
}
if p.OriginContent != nil {
data["originContent"] = *p.OriginContent
}
if p.GenerateImage != nil {
data["generateImage"] = *p.GenerateImage
}
if p.GenerateVideo != nil {
data["generateVideo"] = *p.GenerateVideo
}
if p.GenerateVideoCover != nil {
data["generateVideoCover"] = *p.GenerateVideoCover
}
if p.Status != nil {
data["status"] = *p.Status
// 审核后上线时间为当前时间
if post.Status != *p.Status && *p.Status == 1 {
data["reviewAt"] = time.Now()
}
}
if p.Reason != nil {
data["reason"] = *p.Reason
}
if p.SortCode != nil {
data["sortCode"] = *p.SortCode
}
data["updatedAt"] = time.Now()
if _, err = aiplazadata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID []primitive.ObjectID `json:"ids" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := aiplazadata.DeleteData(nil, p.ID...); err != nil {
return err
}
return nil
}
type WebBatchUpdateReq struct {
Ids []primitive.ObjectID `json:"ids" binding:"required"`
Status *int `json:"status"` // 0-待审核 1-审核通过 2-拒绝 3-下架
Reason *string `json:"reason"` // 原因
}
// Update 更新数据
func (p *WebBatchUpdateReq) Update() error {
data := make(map[string]interface{})
if p.Status != nil {
data["status"] = *p.Status
if *p.Status == 1 {
data["reviewAt"] = time.Now()
}
}
if p.Reason != nil {
data["reason"] = *p.Reason
}
data["updatedAt"] = time.Now()
if _, err := aiplazamod.UpdateByIds(nil, p.Ids, data); err != nil {
return err
}
return nil
}
+80
View File
@@ -0,0 +1,80 @@
package annouser
import (
"fmt"
"time"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/annoumod"
"91porn-server/web/vidhelp"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// GetAnnouList 获取公告列表
func GetAnnouList(req annoumod.ListReq) (code stderr.Code, data interface{}) {
if len(req.Sort) == 0 {
req.Sort = "createdAt"
}
desc := -1
if req.Desc == 1 {
desc = req.Desc
}
aInfos, _, total, err := annoumod.GetAnnou(req.PageNumber, req.PageSize, req.Sort, desc)
if err != nil {
return stderr.ErrDbQueryError, nil
}
return stderr.Success, annoumod.ListResp{AInfos: aInfos, Total: total}
}
// UpdateAnnou 更新公告信息
func UpdateAnnou(req annoumod.EditReq) (code stderr.Code, data interface{}) {
oid, err := primitive.ObjectIDFromHex(req.ID)
if err != nil {
log.Error(fmt.Sprintf("exchange string(%s) to objectID error.", req.ID))
return stderr.ErrParamError, nil
}
m := vidhelp.Struct2Map(req.EditInfo)
cnt, err := annoumod.UpdateAnnou(oid, m)
if err != nil {
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, annoumod.OpeResp{Count: cnt}
}
// DeleteAnnou 删除公告
func DeleteAnnou(ids []string) (code stderr.Code, data interface{}) {
oids := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
log.Error(fmt.Sprintf("exchange string(%s) to objectID error.", id))
continue
}
oids = append(oids, oid)
}
cnt, err := annoumod.DeleteAnnou(oids)
if err != nil {
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, annoumod.OpeResp{Count: cnt}
}
// AddAnnou 添加公告
func AddAnnou(req annoumod.AddReq) (code stderr.Code, data interface{}) {
infos := []annoumod.Annou{annoumod.Annou{
Title: req.Title,
Content: req.Content,
Cover: req.Cover,
Type: req.Type,
Href: req.Href,
Active: req.Active,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}}
if err := annoumod.AddAnnou(infos); err != nil {
return stderr.ErrDbInsertError, ""
}
return stderr.Success, nil
}
+88
View File
@@ -0,0 +1,88 @@
package annouser
import (
"fmt"
"time"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/annoumod"
"91porn-server/web/vidhelp"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
// GetAnnounceList 获取会员中心跑马灯列表
func GetAnnounceListApi() (code stderr.Code, announce string) {
aInfos, err := annoumod.GetAnnounceApi("createdAt", -1)
if err != nil && err != mongo.ErrNoDocuments {
return stderr.ErrDbQueryError, ""
}
if err == mongo.ErrNoDocuments || len(aInfos) <= 0 {
return stderr.Success, ""
}
return stderr.Success, aInfos[0].Content
}
// GetAnnounceList 获取会员中心跑马灯列表
func GetAnnounceList() (code stderr.Code, data []annoumod.Announce) {
aInfos, err := annoumod.GetAnnounce("createdAt", -1)
if err != nil && err != mongo.ErrNoDocuments {
return stderr.ErrDbQueryError, nil
}
if err == mongo.ErrNoDocuments || len(aInfos) <= 0 {
return stderr.Success, nil
}
return stderr.Success, aInfos
}
// UpdateAnnounce 更新会员中心跑马灯信息
func UpdateAnnounce(req annoumod.EditAnnounceReq) (code stderr.Code, data interface{}) {
oid, err := primitive.ObjectIDFromHex(req.ID)
if err != nil {
log.Error(fmt.Sprintf("exchange string(%s) to objectID error.", req.ID))
return stderr.ErrParamError, nil
}
m := vidhelp.Struct2Map(req.EditAnnounceInfo)
cnt, err := annoumod.UpdateAnnounce(oid, m)
if err != nil {
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, annoumod.OpeResp{Count: cnt}
}
// DeleteAnnounce 删除会员中心跑马灯
func DeleteAnnounce(ids []string) (code stderr.Code, data interface{}) {
oids := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
log.Error(fmt.Sprintf("exchange string(%s) to objectID error.", id))
continue
}
oids = append(oids, oid)
}
cnt, err := annoumod.DeleteAnnounce(oids)
if err != nil {
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, annoumod.OpeResp{Count: cnt}
}
// AddAnnounce 添加会员中心跑马灯
func AddAnnounce(req annoumod.AddAnnounceReq) (code stderr.Code) {
infos := []annoumod.Announce{annoumod.Announce{
ID: primitive.NewObjectID(),
Content: req.Content,
Url: req.Url,
Type: annoumod.UserCenter,
Active: req.Active,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}}
if err := annoumod.AddAnnounce(infos); err != nil {
return stderr.ErrDbInsertError
}
return stderr.Success
}
+57
View File
@@ -0,0 +1,57 @@
package authorser
import (
"fmt"
"91porn-server/models/v/adminmod"
"91porn-server/models/v/authoritymod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ObjectID ObjectID
type ObjectID = primitive.ObjectID
// SetAuthority SetAuthority
func SetAuthority(adminID ObjectID, authID ObjectID) error {
existed, err := adminmod.ExistsByID(adminID)
if err != nil {
return fmt.Errorf("service SetAuthority adminID Exists error: " + err.Error())
}
if !existed {
return fmt.Errorf("service SetAuthority adminID is not exist")
}
existed, err = authoritymod.ExistsByID(authID)
if err != nil {
return fmt.Errorf("service SetAuthority authID Exists error: " + err.Error())
}
if !existed {
return fmt.Errorf("service SetAuthority authID is not exist")
}
authority, err := authoritymod.FindOne(authoritymod.AuthorityDoc{
ID: authID,
})
if err != nil {
return fmt.Errorf("service SetAuthority authID is not existed" + err.Error())
}
if err = adminmod.UpdateByID(adminID, adminmod.AdminDoc{
Role: &authority.Role,
}); err != nil {
return fmt.Errorf("service SetAuthority faild " + err.Error())
}
return nil
}
func UnsetAuthority(adminID ObjectID) error {
existed, err := adminmod.ExistsByID(adminID)
if err != nil {
return fmt.Errorf("author UnsetAuthority adminID Exists error: " + err.Error())
}
if !existed {
return fmt.Errorf("service UnsetAuthority adminID is not exist")
}
if err = adminmod.UnsetPrivilege(adminID); err != nil {
return fmt.Errorf("service UnsetAuthority faild " + err.Error())
}
return nil
}
+22
View File
@@ -0,0 +1,22 @@
package backpack_service
import "91porn-server/models/v/backpackmod"
func QueryAll(in *backpackmod.QueryAllWebCond) (*backpackmod.QueryAllRes, error) {
var out = &backpackmod.QueryAllRes{}
count, err := backpackmod.QueryAllCount(in.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*backpackmod.Backpack{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = backpackmod.QueryAllList(in.Filter(), in.Options())
if err != nil {
return nil, err
}
return out, nil
}
+129
View File
@@ -0,0 +1,129 @@
package bannerjumpser
import (
"time"
"91porn-server/models/cache/bannerjumpdata"
"91porn-server/models/commod"
"91porn-server/models/v/bannerjumpmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []bannerjumpmod.BannerJump `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
res.List, res.Total, res.HasNext, err = bannerjumpmod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebCreateReq struct {
Position int `json:"position" form:"position" binding:"required"` // 位置 1-视频播放器下方 2-收益页 3-首页浮窗
Banner string `json:"banner" form:"banner" binding:"required"` // BANNER图片
Title string `json:"title" form:"title" binding:"required"` // 标题
Url string `json:"url" form:"url" binding:"required"` // 跳转地址
StartAt time.Time `json:"startAt" form:"startAt" binding:"required"` // 开始时间
EndAt time.Time `json:"endAt" form:"endAt" binding:"required"` // 结束时间
Weight int `json:"weight" form:"weight" binding:"required"` // 权重
Enable bool `json:"enable" form:"enable" binding:"required"` // 是否启用
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := bannerjumpmod.BannerJump{
Position: p.Position,
Banner: p.Banner,
Title: p.Title,
Url: p.Url,
StartAt: p.StartAt,
EndAt: p.EndAt,
Weight: p.Weight,
Enable: p.Enable,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 创建数据
if _, err := bannerjumpdata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Position *int `json:"position"` // 位置 1-视频播放器下方 2-收益页 3-首页浮窗
Banner *string `json:"banner"` // BANNER图片
Title *string `json:"title"` // 标题
Url *string `json:"url"` // 跳转地址
StartAt *time.Time `json:"startAt"` // 开始时间
EndAt *time.Time `json:"endAt"` // 结束时间
Weight *int `json:"weight"` // 权重
Enable *bool `json:"enable"` // 是否启用
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := bannerjumpmod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Position != nil {
data["position"] = *p.Position
}
if p.Banner != nil {
data["banner"] = *p.Banner
}
if p.Title != nil {
data["title"] = *p.Title
}
if p.Url != nil {
data["url"] = *p.Url
}
if p.StartAt != nil {
data["startAt"] = *p.StartAt
}
if p.EndAt != nil {
data["endAt"] = *p.EndAt
}
if p.Weight != nil {
data["weight"] = *p.Weight
}
if p.Enable != nil {
data["enable"] = *p.Enable
}
data["updatedAt"] = time.Now()
if _, err = bannerjumpdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := bannerjumpdata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
+59
View File
@@ -0,0 +1,59 @@
package certificateser
import (
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/common/ysqr"
"91porn-server/models/v/usermod"
"91porn-server/web/middleware/authweb"
"github.com/gin-gonic/gin"
)
// QrCode doc
// @Summary 二维码
// @Description 二维码
// @Tags 凭证
// @Accept mpfd,json
// @Produce json,html
// @Param token formData string true "令牌"
// @Success 200 {string} json "{"msg": "操作成功", "content": "", user:{}}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /ext/certificate/qr [post]
func QrCode(c *gin.Context) {
var args struct {
Token string `form:"token" json:"token" binding:"required"` //token
}
if err := c.ShouldBind(&args); err != nil {
common.ServeJSON(c, stderr.ErrParamError, "certificateser QrCode arg error "+err.Error())
return
}
claims, err := authweb.ParseWebClaims(args.Token)
if err != nil {
log.Error("activity InvitInfo ParseWebClaims error", log.Any("token", args.Token), log.E(err))
common.ServeJSON(c, stderr.InvalidToken, "")
return
}
user, _ := usermod.FindUserByUID(claims.UID)
if user == nil {
common.ServeJSON(c, stderr.AdminIsNotExist, "")
return
}
content := ysqr.Content{
UID: user.UID,
T: ysqr.Login,
LoginClaims: ysqr.LoginClaims{
DevID: user.DevID,
},
}
u := gin.H{
"uid": user.UID,
"name": user.Name,
"portrait": user.Portrait,
}
common.ServeJSON(c, stderr.Success, gin.H{
"content": content.String(authweb.GetTokenSecret()),
"user": u,
})
}
@@ -0,0 +1,219 @@
package chat_message_ser
import (
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/messagemod"
"91porn-server/models/v/sessionmod"
"91porn-server/models/v/usermod"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"time"
)
func List(req *messagemod.WebListRequest) (interface{}, stderr.Code) {
var data = map[string]interface{}{
"list": []interface{}{},
"count": 0,
}
count, err := messagemod.CountDocument(req.Filter())
if err != nil {
return nil, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
list, err := messagemod.QueryAllDocument(req.Filter(), req.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["count"] = count
data["list"] = list
return data, stderr.Success
}
func Del(req *messagemod.WebDelReq) stderr.Code {
data, err := messagemod.FindOneByFilter(req.Filter())
if err != nil {
return stderr.ErrDbQueryError
}
if data.ID.IsZero() {
return stderr.CodeEmptyData
}
err = messagemod.RemoveDocument(req.Filter())
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// AddPrivateLetter 新增私信消息
func AddPrivateLetter(req *messagemod.SendMsg) stderr.Code {
if req.Sender == 0 || req.Receiver == 0 {
return stderr.ErrParamError
}
data, err := usermod.FindUserByUID(req.Sender)
if err != nil || data == nil {
log.Warn(fmt.Sprintf("messageser Sender is null"), log.Any("Sender", req.Sender))
return stderr.CommentUserInfoErr
}
if req.Sender == req.Receiver {
log.Warn(fmt.Sprintf("messageser req.Sender == req.Receiver"), log.Any("Sender", req.Sender), log.Any("Receiver", req.Receiver))
return stderr.NoSelfAddMsg
}
//查询被私信的用户是否存在
takeUser, err := usermod.FindUserByUID(req.Receiver)
if err != nil {
return stderr.CommentUserInfoErr
}
if takeUser == nil {
log.Warn(fmt.Sprintf("messageser Receiver is null"), log.Any("Receiver", req.Receiver))
return stderr.CommentUserInfoErr
}
//会话id
sessionId := ""
//新增私聊信息之前, 先查询是否有会话信息
cond := bson.M{"$or": []bson.M{bson.M{"sendUid": req.Sender, "takeUid": req.Receiver}, bson.M{"sendUid": req.Receiver, "takeUid": req.Sender}}}
session, err := sessionmod.FindOneByFilter(cond)
if err != nil {
log.Warn(fmt.Sprintf("messageser Add sessionmod.FindOneByFilter error:%+v:", err), log.Any("Sender", req.Sender), log.Any("Receiver", req.Receiver))
return stderr.ErrDbQueryError
}
t := time.Now()
if session.ID.IsZero() {
sessionId = common.UUID()
//新增session
s := &sessionmod.Session{
SendUid: req.Sender,
TakeUid: req.Receiver,
SessionId: sessionId,
CreatedAt: t,
UpdatedAt: t,
}
err := sessionmod.InsertOne(nil, s)
if err != nil {
log.Warn(fmt.Sprintf("messageser Add sessionmod.InsertOne error:%+v:", err), log.Any("session", s), log.Any("Sender", req.Sender), log.Any("Receiver", req.Receiver))
return stderr.ErrDbInsertError
}
} else {
sessionId = session.SessionId
}
code := PublicAddMsg(messagemod.PublicAddMsg{
SendUid: req.Sender,
TakeUid: req.Receiver,
SessionId: sessionId,
Content: req.Content,
//ImgUrl: req.ImgUrl,// 暂时不支持图片
MsgType: req.MsgType,
ObjId: req.ObjId,
ObjName: req.ObjName,
})
if code != stderr.Success {
return code
}
common.Go(func() {
//更新会话预览消息
content := req.Content
// 暂时不支持图片
//if len(req.ImgUrl) > 0 && content == "" {
// content = "发送了一张图片"
//}
err := sessionmod.UpdOneByFilter(nil, bson.M{"sessionId": sessionId}, bson.M{"$set": bson.M{"preContent": content, "updatedAt": t}})
if err != nil {
log.Warn(fmt.Sprintf("messageser Add messagemod.UpdOneByFilter error:%+v:", err), log.Any("sessionId", sessionId), log.Any("preContent", req.Content), log.Any("Sender", req.Sender), log.Any("Receiver", req.Receiver))
}
})
return stderr.Success
}
// PublicAddMsg 添加消息公共方法
func PublicAddMsg(prams messagemod.PublicAddMsg) stderr.Code {
//查询用户信息
user, err := usermod.FindUserByUID(prams.SendUid)
if err != nil {
return stderr.ErrDbQueryError
}
if user == nil || user.HasLocked {
return stderr.UserIsNotExists
}
now := time.Now()
//新增消息
msg := &messagemod.Message{
SendUid: prams.SendUid,
TakeUid: prams.TakeUid,
MsgType: prams.MsgType,
Content: prams.Content,
ImgUrl: prams.ImgUrl,
IsRead: false,
CreatedAt: now,
UpdatedAt: now,
ObjId: prams.ObjId,
ObjName: prams.ObjName,
}
// 是否加入未读提醒
needTip := false
switch prams.MsgType {
case messagemod.PrivateLetterMsg:
//私信
msg.SessionId = prams.SessionId
needTip = true
case messagemod.OfficialPrivateLetterMsg:
//官方私信
msg.SessionId = prams.SessionId
msg.ContentType = prams.ContentType
needTip = true
case messagemod.FollowMsg:
//关注
msg.Content = "关注了你"
needTip = true
case messagemod.LikeMsg:
//点赞
msg.Content = "点赞了你的帖子"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
needTip = true
case messagemod.ForwardMsg: //转发
msg.Content = "转发了你的帖子"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
case messagemod.CommentMsg: //评论
msg.Content = "评论了你的帖子"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
needTip = true
case messagemod.DesireMsg:
msg.Content = "回复了你的工单"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
needTip = true
case messagemod.LikeCommentMsg: // 点赞评论
msg.Content = "点赞了你的评论"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
case messagemod.ReplyCommentMsg: // 回复评论
msg.Content = "回复了你的评论"
msg.ObjId = prams.ObjId
msg.ObjName = prams.ObjName
needTip = true
}
_, err = messagemod.InsertOne(nil, msg)
if err != nil {
log.Warn(fmt.Sprintf("messageser Add messagemod.InsertOne error:%+v:", err), log.Any("message", msg))
return stderr.ErrDbQueryError
}
go func() {
err = usermod.IncDynamic(prams.TakeUid)
}()
// 更新提示状态
if needTip {
//_ = message.SetTip(prams.TakeUid)
}
return stderr.Success
}
+119
View File
@@ -0,0 +1,119 @@
package chatrobotser
import (
"time"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/chatrobotmod"
"github.com/jinzhu/copier"
)
// 获取机器人配置列表
func GetRobotConfList(param chatrobotmod.ReqConfList) (code stderr.Code, data interface{}, err error) {
var skip = int64((param.PageNumber - 1) * param.PageSize)
var limit = int64(param.PageSize)
total, cList, err := chatrobotmod.GetRobotConf(param.Type, skip, limit)
if err != nil {
code = stderr.Failure
return
}
if cList == nil {
data = commod.ListResp{List: []chatrobotmod.ChatRobot{}}
} else {
list := make([]chatrobotmod.RespList, len(cList))
for i, v := range cList {
tmp := chatrobotmod.RespList{}
if err = copier.Copy(&tmp, &v); err != nil {
code = stderr.Failure
return
}
list[i] = tmp
}
data = commod.ListResp{List: list, Total: total}
}
code = stderr.Success
return
}
// 添加配置
func AddRobotConf(param chatrobotmod.ReqAdd) (code stderr.Code, data interface{}, err error) {
if code, err = addCheck(param.Type); err != nil || code != stderr.Success {
return
}
switch param.Type {
case chatrobotmod.Comment: // 评论机器人
return addCmtRobotConf(param.Type, param.ReqAddCmt)
default:
code = stderr.RobotTypeInvalid
return
}
}
func addCheck(rType chatrobotmod.EnumRobotType) (stderr.Code, error) {
isExists, err := chatrobotmod.ConfExists(rType)
if err != nil {
return stderr.Failure, err
}
if isExists {
return stderr.RobotTypeExisted, nil
}
return stderr.Success, nil
}
// 新增评论机器人配置
func addCmtRobotConf(rType chatrobotmod.EnumRobotType, param chatrobotmod.ReqAddCmt) (code stderr.Code, data interface{}, err error) {
var now = time.Now()
var doc = chatrobotmod.ChatRobot{
Type: rType,
IsActive: param.IsActive,
CreatedAt: now,
UpdatedAt: now,
CommentRobot: chatrobotmod.CommentRobot{
From: param.From,
To: param.To,
TotalLimit: param.TotalLimit,
PureLimit: param.PureLimit,
Frequency: param.Frequency,
MaxCommentNum: param.MaxCommentNum,
MaxFakeLikes: param.MaxFakeLikes,
MinFakeLikes: param.MinFakeLikes,
MaxFakePlayCount: param.MaxFakePlayCount,
MinFakePlayCount: param.MinFakePlayCount,
},
}
if err = chatrobotmod.Insert(doc); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
// 更新机器人配置
func UpdateRobotConf(param chatrobotmod.ReqUpdate) (code stderr.Code, data interface{}, err error) {
switch param.Type {
case chatrobotmod.Comment: // 评论机器人
return updateCmtRobotConf(param.Type, param.ReqUpdateCmt)
default:
code = stderr.RobotTypeInvalid
return
}
}
// 更新评论机器人配置
func updateCmtRobotConf(rType chatrobotmod.EnumRobotType, param chatrobotmod.ReqUpdateCmt) (code stderr.Code, data interface{}, err error) {
doc := chatrobotmod.CmtUpdateDoc{}
if err = copier.Copy(&doc, &param); err != nil {
code = stderr.Failure
return
}
doc.UpdatedAt = time.Now()
if err = chatrobotmod.UpdateCmtRobotConf(rType, doc); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
+424
View File
@@ -0,0 +1,424 @@
package commentser
import (
"91porn-server/app/service/taskser"
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/cmtmod"
"91porn-server/models/v/dailytaskmod"
"91porn-server/models/v/locmod"
"91porn-server/models/v/messagemod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"91porn-server/models/v/walletmod"
"91porn-server/web/service/chat_message_ser"
"91porn-server/web/vidhelp"
"91porn-server/web/webg"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// GetCommentList 后台获取评论列表
func GetCommentList(vidID string, cUID uint64, content string, isRobot, isAdvertiser *bool, status *int, page commod.Page) (data interface{}, err error) {
cList, count, err := cmtmod.CmtFindCommentsAndCount(vidID, cUID, content, isRobot, isAdvertiser, status, page)
if err != nil {
log.Error("web service comment GetCommentList FindUserByUID error", log.E(err), log.Any("vidID", vidID), log.Any("cUID", cUID), log.Any("content", content))
return
}
var cmtList []cmtmod.WebCmtRespList
var uids []uint64
for _, v := range cList {
// 获取评论的用户是否已禁言
usr, err := usermod.FindUserByUID(v.UserID)
if err != nil || usr == nil {
log.Error("web service comment GetCommentList FindUserByUID error", log.E(err), log.Any("uid", v.UserID))
continue // 避免测试服脏数据造成无数据返回
}
tmp := cmtmod.WebCmtRespList{
ID: v.ID,
ObjID: v.ObjID,
UserID: v.UserID,
LikeCount: v.LikeCount,
Content: v.Content,
Level: v.Level,
Status: v.Status,
IsDelete: v.IsDelete,
LinkStr: v.LinkStr,
IsBaned: usr.HasBanned,
IsGodComment: v.IsGodComment,
Portrait: usr.Portrait,
Name: usr.Name,
CreatedAt: v.CreatedAt,
IsRobot: v.IsRobot,
HasLocked: usr.HasLocked,
IsAdvertiser: usr.IsAdvertiser,
Image: v.Image,
}
cmtList = append(cmtList, tmp)
uids = append(uids, v.UserID)
}
if cmtList == nil {
cmtList = []cmtmod.WebCmtRespList{}
}
resp := make(map[string]interface{})
resp["list"] = cmtList
resp["total"] = count
resp["uids"] = uids
data = resp
return
}
// 后台评论管理屏蔽评论
func DeleteCommentsByID(ids []primitive.ObjectID, uid uint64, content string) (err error) {
var cmtList []cmtmod.Comment
if uid != 0 {
// 获取评论
if cmtList, err = cmtmod.GetCommentsByUID(uid); err != nil {
return
}
if err = cmtmod.DelCommentsByUID(uid); err != nil {
return
}
} else if content != "" {
if cmtList, err = cmtmod.GetCommentsByContent(content); err != nil {
return
}
if err = cmtmod.DelCommentsByContent(content); err != nil {
return
}
} else {
if ids == nil || len(ids) == 0 {
return
}
if cmtList, err = cmtmod.GetCommentsByIDS(ids); err != nil {
return
}
if err = cmtmod.DelCommentsByIDS(ids); err != nil {
return
}
}
// 评论数更新
for _, v := range cmtList {
// 视频评论数-1
_ = vidhelp.DecCommentCount(v.ObjID)
// 删除子评论
if v.Level == 1 {
var childIDS []primitive.ObjectID
// 获取子评论
childCmtList, _ := cmtmod.GetCommentsByCID(v.ObjID, v.ID)
for _, v1 := range childCmtList {
childIDS = append(childIDS, v1.ID)
// 视频评论数-1
_ = vidhelp.DecCommentCount(v1.ObjID)
}
if childIDS == nil || len(childIDS) == 0 {
continue
}
// 删除评论
_ = cmtmod.DelCommentsByIDS(childIDS)
}
// 评论数-1
if v.Level == 2 && !v.CID.IsZero() {
_ = cmtmod.CmtDecreaseCommentCountByID(v.CID)
}
}
return
}
// 后台获取评论详情
func GetCommentInfo(id primitive.ObjectID) (data interface{}, err error) {
return cmtmod.CmtFindOneByID(id)
}
// AccessComment 后台评论通过审核
func AccessComment(id primitive.ObjectID) (err error) {
cmt, err := cmtmod.CmtFindOneByID(id)
if err != nil || cmt.ID.IsZero() {
log.Error("web service comment AccessComment CmtFindOneByID error", log.E(err), log.Any("id", id))
return
}
err = cmtmod.CmtUpdateCommentStatusByID(id, 1)
if err != nil {
log.Error("web service comment GetCommentList CmtUpdateCommentStatusByID error", log.E(err), log.Any("uid", id))
return
}
// 审核通过增加评论数
err = vidmod.IncCommentCount(cmt.ObjID)
if err != nil {
log.Error("web service comment GetCommentList IncCommentCount error", log.E(err), log.Any("uid", id))
return
}
return
}
// BatchAccessComment 批量后台评论通过审核
func BatchAccessComment(ids []primitive.ObjectID, status int) (err error) {
cmt, err := cmtmod.CmtFindOneByIds(ids)
if err != nil || len(cmt) <= 0 {
log.Error("web service comment AccessComment CmtFindOneByIds error", log.E(err), log.Any("ids", ids))
return
}
err = cmtmod.PassCommentStatusByIds(ids, status)
if err != nil {
log.Error("web service BatchAccessComment PassCommentStatusByIds error", log.E(err))
return
}
return
}
// PublishComment 发表评论 统一以作者身份回复
func PublishComment(param cmtmod.PublishReqInfo) (code stderr.Code, err error) {
videoInfo, err := vidmod.GetVideoInfo(param.ObjID.Hex())
if err != nil || videoInfo.ID.IsZero() {
code = stderr.CommentErrSendFail
code.SetMsg("current video is not exist or find error")
return code, err
}
var cmtMod cmtmod.Comment
isAuthor := false
if videoInfo.PublisherID == param.ToUserID {
isAuthor = true
}
if code, cmtMod, err = getTrackInfo(videoInfo, "", isAuthor, param); code != stderr.Success || err != nil {
return code, err
}
// 保存到数据库
id, err := cmtmod.CmtInsertOne(&cmtMod)
if err != nil {
code = stderr.CommentErrSendFail
return code, err
}
if err = setGid(param.CID, param.RID, id); err != nil {
code = stderr.CommentErrSendFail
return code, err
}
// 增加评论数
incCommentCount(param.CID, param.ObjID)
// 获取该评论
return
}
// 获取相关信息
func getTrackInfo(videoInfo vidmod.VideoModel, ip string, isAuthor bool, param cmtmod.PublishReqInfo) (code stderr.Code, data cmtmod.Comment, err error) {
var cid primitive.ObjectID
var publiserID = videoInfo.PublisherID
if param.CID != "" {
if cid, err = primitive.ObjectIDFromHex(param.CID); err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
}
loc, _ := locmod.GetLocationInfoByID(videoInfo.Location)
now := time.Now()
data = cmtmod.Comment{
ObjID: param.ObjID,
CID: cid,
PublisherID: publiserID,
SortCnt: 0,
ToUserID: param.ToUserID,
Content: param.Content,
OrgContent: param.Content,
UserID: param.ToUserID,
Status: 1,
Level: param.Level,
AuthorMark: isAuthor,
IsAuthor: isAuthor,
IsDelete: false,
CommentCount: 0,
LikeCount: 0,
IPAddr: ip,
City: loc.City,
IsRobot: true,
CreatedAt: now,
}
if param.ToUserID != 0 {
if param.Level == 1 {
user, err := usermod.FindUserByUID(param.ToUserID)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
data.UserID = user.UID
} else {
rCmt, err := cmtmod.CmtFindOneByID(cid)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
user, err := usermod.FindUserByUID(rCmt.UserID)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
data.UserID = param.ToUserID
data.ToUserID = rCmt.UserID
data.ToUserName = user.Name
}
}
if param.RID != "" {
rid, err := primitive.ObjectIDFromHex(param.RID)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
rCmt, err := cmtmod.CmtFindOneByID(rid)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
user, err := usermod.FindUserByUID(rCmt.UserID)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
if rCmt.Level != 1 {
// 如果回复的不是1级,则需要写入rid
data.RID = rid
}
data.GID = rCmt.GID
data.AuthorMark = rCmt.AuthorMark
data.SortCnt = rCmt.SortCnt
data.Type = cmtmod.CmtTypeReply
data.PublisherID = rCmt.UserID
data.ToUserName = user.Name
data.ToUserID = rCmt.UserID
if !rCmt.CID.IsZero() {
data.CID = rCmt.CID
}
} else if param.RID == "" && !cid.IsZero() {
data.Type = cmtmod.CmtTypeComment
cCmt, err := cmtmod.CmtFindOneByID(cid)
if err != nil {
code = stderr.CommentErrSendFail
return code, data, err
}
data.PublisherID = cCmt.UserID
} else {
data.PublisherID = videoInfo.PublisherID
data.Type = cmtmod.CmtTypeVideo
}
code = stderr.Success
return
}
// 设置分组id
func setGid(cid string, rid string, id primitive.ObjectID) (err error) {
if cid != "" && rid == "" {
return cmtmod.SetGid(id)
}
return
}
// 增加评论数
func incCommentCount(cid string, objID primitive.ObjectID) {
if cid != "" {
var objCid primitive.ObjectID
objCid, err := primitive.ObjectIDFromHex(cid)
if err != nil {
return
}
// 根据cid获取评论信息
if toComment, err := cmtmod.CmtFindOneByID(objCid); err != nil || (toComment.Level != 1 && toComment.Level != 2) {
return
}
if err := cmtmod.CmtIncreaseCommentCountByID(objCid); err != nil {
return
}
}
// 增加视频评论数
if err := vidhelp.IncCommentCount(objID); err != nil {
return
}
}
// UpdateComment 后台评论设置
func UpdateComment(in *cmtmod.EditCond) (err error) {
cmt, err := cmtmod.CmtFindOneByID(in.ID)
if err != nil || cmt.ID.IsZero() {
log.Error("web service comment AccessComment CmtFindOneByID error", log.E(err), log.Any("id", in.ID))
return errors.New("comment is err or null")
}
if in.LinkStr == nil && cmt.Level != 1 && in.LikeCount == nil {
return errors.New("comment level is not 1")
}
if in.LinkStr == nil && cmt.IsAuthor && in.LikeCount == nil {
return errors.New("author not allow godComment")
}
if in.LinkStr == nil && in.IsGodComment == nil && in.LikeCount == nil {
return errors.New("update params is null")
}
// 如果设置为神评论
// 判断是否已经发送过该评论的神评论奖励(避免修改为普通评论后又修改为神评论,奖励多次发放的情况)
if in.IsGodComment != nil && *in.IsGodComment == true && !cmt.GodCommentAward {
in.GodCommentAward = true
}
err = cmtmod.Update(in.Filter(), in.Update())
if err != nil {
log.Error("web service comment GetCommentList CmtUpdateCommentStatusByID error", log.E(err), log.Any("id", in.ID))
return
}
/********************* 以下是神评相关逻辑 **********************/
// 完成任务,评论被设置为神评
if in.IsGodComment != nil && *in.IsGodComment == true {
// 完成评论被设置为神评
common.Go(func() {
err = taskser.CompleteDailyTask(nil, cmt.UserID, dailytaskmod.CommentSetGod)
if err != nil {
log.Error("web 设置神评,完成每日任务【评论被设置为神评】失败", log.E(err), log.Any("id", in.ID))
return
}
})
}
// 获取神评论奖励积分配置
if webg.Conf.GodCommentAward <= 0 {
return stderr.Success
}
// 查询出评论的帖子信息
video, err := vidmod.GetVideoByID(cmt.ObjID)
if err != nil || video == nil {
return
}
objName := fmt.Sprintf("[%v]", video.Title)
content := fmt.Sprintf("恭喜您在%v中评论获得神评,获得%v积分", objName, webg.Conf.GodCommentAward)
// 发放积分
walletInfo, err := walletmod.CreditIntegral(nil, webg.Conf.GodCommentAward, cmt.UserID)
if err != nil {
return err
}
log := &txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: cmt.UserID,
Integral: webg.Conf.GodCommentAward,
ActualIntegral: float64(webg.Conf.GodCommentAward),
TranType: txnmod.GodCommentAward.Key(),
TranTypeInt: int64(txnmod.GodCommentAward),
Desc: fmt.Sprintf("%s-积分[%d个]", txnmod.GodCommentAward.Key(), webg.Conf.GodCommentAward),
RealIntegral: walletInfo.RealIntegral(),
}
txnmod.InsertTransactionLog(nil, log)
// 发放私信告诉用户获得了积分
code := chat_message_ser.AddPrivateLetter(&messagemod.SendMsg{
Sender: 100001,
Receiver: cmt.UserID,
Content: content,
MsgType: messagemod.OfficialPrivateLetterMsg,
ObjId: video.ID,
ObjName: objName,
})
return code
}
+417
View File
@@ -0,0 +1,417 @@
package commentser
import (
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/models/v/cmtmod"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"fmt"
"strconv"
"strings"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 缓存随机用户列表,避免每次都查询数据库
var (
cachedUsers []*usermod.User
cachedUsersMu sync.RWMutex
cachedUsersTime time.Time
cachedUsersTTL = 10 * time.Minute
)
func getReplyUsers() ([]*usermod.User, error) {
cachedUsersMu.RLock()
if len(cachedUsers) > 0 && time.Since(cachedUsersTime) < cachedUsersTTL {
users := cachedUsers
cachedUsersMu.RUnlock()
return users, nil
}
cachedUsersMu.RUnlock()
cachedUsersMu.Lock()
defer cachedUsersMu.Unlock()
// double check
if len(cachedUsers) > 0 && time.Since(cachedUsersTime) < cachedUsersTTL {
return cachedUsers, nil
}
users, _, err := usermod.FetchList(bson.M{"uid": bson.M{"$lte": 300000}}, options.Find().SetLimit(1000))
if err != nil {
return nil, err
}
cachedUsers = users
cachedUsersTime = time.Now()
return users, nil
}
func PullCommentList(status string, limit int) (cmtmod.PullResp, error) {
var resp cmtmod.PullResp
filter := bson.M{}
if limit <= 0 {
limit = 20
}
if limit > 100 {
limit = 100
}
// 判断评论状态,抓取未审核的评论
if status == "pending" {
filter["status"] = 2
}
// 拉取已经审核成功且无子评论的一级评论
if status == "pending_reply" {
filter["status"] = 1
filter["level"] = 1
}
// 查询评论列表
opts := options.FindOptions{}
opts.SetSort(bson.D{{Key: "createdAt", Value: 1}}).SetLimit(int64(limit))
commentList, err := cmtmod.GetList(filter, &opts)
if err != nil {
log.Error("pull comment list fail", log.E(err))
return resp, err
}
resp = cmtmod.PullResp{
Code: 200,
Msg: "success",
Data: nil,
}
for _, comment := range commentList {
pullComment := cmtmod.PullComment{
Id: comment.ID.Hex(),
UserId: strconv.FormatUint(comment.UserID, 10),
Nickname: "",
Content: comment.Content,
CreatedAt: comment.CreatedAt.Format("2006-01-02 15:04:05"),
Ip: "",
Context: cmtmod.Context{
ContentId: comment.ObjID.Hex(),
ContentTitle: comment.Title,
},
}
switch comment.ObjType {
case cmtmod.OTypeVideo:
pullComment.Context.ContentType = 1
case cmtmod.OTypeCartoon:
pullComment.Context.ContentType = 3
default:
pullComment.Context.ContentType = 2
}
resp.Data = append(resp.Data, pullComment)
}
log.Info("pull comment list success", log.Any("resp", resp))
return resp, nil
}
func CommentApprove(comments cmtmod.ApproveReq) (err error) {
var passCommentIDs []primitive.ObjectID
var reviewCommentIDs []primitive.ObjectID
// 遍历评论列表
for _, comment := range comments.Reviews {
oid, parseErr := primitive.ObjectIDFromHex(comment.CommentId)
if parseErr != nil || oid.IsZero() {
continue
}
reviewCommentIDs = append(reviewCommentIDs, oid)
// 审核通过评论
if comment.Action == "approve" {
passCommentIDs = append(passCommentIDs, oid)
} else if comment.Action == "reject" {
// 评论没有通过,更新评论状态,以及评论没有通过原因
err = cmtmod.Update(bson.M{"_id": oid}, bson.M{"$set": bson.M{"status": 4, "reason": comment.Reason, "desc": "Agent system review reject"}})
if err != nil {
log.Error("web service comment CommentApprove cmtmod.Update fail", log.E(err), log.Any("id", oid))
return
}
}
}
if len(passCommentIDs) > 0 {
err = cmtmod.UpdateMany(bson.M{"_id": bson.M{"$in": passCommentIDs}}, bson.M{"$set": bson.M{"status": 1, "desc": "Agent system review"}})
if err != nil {
log.Error("web service comment CommentApprove cmtmod.UpdateMany fail", log.E(err), log.Any("_id", passCommentIDs))
return
}
}
if len(reviewCommentIDs) == 0 {
return nil
}
commentList, err := cmtmod.GetCommentsByIDS(reviewCommentIDs)
if err != nil {
log.Error("web service comment CommentApprove cmtmod.GetCommentsByIDS fail", log.E(err), log.Any("ids", reviewCommentIDs))
return err
}
go rebuildVideoAndMediaCommentCount(commentList)
return
}
func ReplyComment(comments cmtmod.RepliesReq) (cmtmod.ReplyResp, error) {
now := time.Now()
// 从缓存获取随机用户列表
userInfos, err := getReplyUsers()
if err != nil {
log.Error("web service comment ReplyComment getReplyUsers fail", log.E(err))
return cmtmod.ReplyResp{Code: 0, Msg: "web service comment ReplyComment Error"}, err
}
if len(userInfos) == 0 {
return cmtmod.ReplyResp{Code: 0, Msg: "web service comment ReplyComment Error"}, fmt.Errorf("no available users for auto reply")
}
// 先收集所有有效的评论ID
oidMap := make(map[primitive.ObjectID]string, len(comments.Replies)) // oid -> reply content
oids := make([]primitive.ObjectID, 0, len(comments.Replies))
for _, reply := range comments.Replies {
oid, parseErr := primitive.ObjectIDFromHex(reply.CommentId)
if parseErr != nil || oid.IsZero() {
continue
}
oids = append(oids, oid)
oidMap[oid] = reply.Content
}
// 批量查询评论信息
cmtInfos, err := cmtmod.CmtFindOneByIds(oids)
if err != nil {
log.Error("web service comment ReplyComment cmtmod.CmtFindOneByIds fail", log.E(err))
return cmtmod.ReplyResp{Code: 0, Msg: "web service comment ReplyComment Error"}, err
}
affectedComments := make([]cmtmod.Comment, 0, len(cmtInfos))
insertDocs := make([]cmtmod.Comment, 0, len(cmtInfos))
for _, cmtInfo := range cmtInfos {
if cmtInfo.ID.IsZero() {
continue
}
content, ok := oidMap[cmtInfo.ID]
if !ok {
continue
}
rand := common.RandInt(0, len(userInfos))
insertDocs = append(insertDocs, cmtmod.Comment{
ID: primitive.NewObjectID(),
ObjType: cmtInfo.ObjType,
Title: cmtInfo.Title,
ObjID: cmtInfo.ObjID,
CID: cmtInfo.ID,
RID: cmtmod.ObjectID{},
GID: cmtmod.ObjectID{},
Type: cmtmod.CmtTypeReply,
PublisherID: 0,
AuthorMark: false,
SortCnt: 0,
UserID: userInfos[rand].UID,
ToUserID: 0,
ToUserName: "",
LikeCount: 0,
CommentCount: 0,
IsGodComment: false,
IsAuthor: false,
IsRobot: false,
Content: content,
OrgContent: "",
Level: 2,
IPAddr: "",
Status: 1,
Reason: "",
Desc: "Agent system reply",
Image: "",
City: "",
LinkStr: "",
QuoteType: "",
QuoteID: primitive.ObjectID{},
QuoteImg: "",
QuoteTitle: "",
IsDelete: false,
IsAdvertiser: false,
CreatedAt: now,
DeletedAt: time.Time{},
})
affectedComments = append(affectedComments, cmtInfo)
}
if len(insertDocs) > 0 {
_, err = cmtmod.CmtInsertMany(insertDocs)
if err != nil {
log.Error("web service comment ReplyComment cmtmod.CmtInsertMany fail", log.E(err))
return cmtmod.ReplyResp{Code: 0, Msg: "web service comment ReplyComment Error"}, err
}
}
rebuildVideoAndMediaCommentCount(affectedComments)
return cmtmod.ReplyResp{
Code: 200,
Msg: "success",
}, nil
}
func rebuildVideoAndMediaCommentCount(comments []cmtmod.Comment) {
if len(comments) == 0 {
return
}
type objInfo struct {
objType cmtmod.OType
objID primitive.ObjectID
}
objMap := make(map[string]objInfo)
for _, cmt := range comments {
if cmt.ObjID.IsZero() {
continue
}
if cmt.ObjType != cmtmod.OTypeVideo && cmt.ObjType != cmtmod.OTypeCartoon {
continue
}
key := string(cmt.ObjType) + "_" + cmt.ObjID.Hex()
objMap[key] = objInfo{
objType: cmt.ObjType,
objID: cmt.ObjID,
}
}
var wg sync.WaitGroup
for _, v := range objMap {
obj := v
wg.Add(1)
common.Go(func() {
defer wg.Done()
rebuildSingleObjCommentCount(obj.objType, obj.objID)
})
}
wg.Wait()
}
func rebuildSingleObjCommentCount(objType cmtmod.OType, objID primitive.ObjectID) {
cond := bson.M{
"objType": objType,
"objID": objID,
"status": 1,
"isAdvertiser": false,
"isDelete": false,
}
count, err := cmtmod.CmtFindCountByCond(cond)
if err != nil {
log.Error("web service comment rebuildSingleObjCommentCount cmtmod.CmtFindCountByCond fail",
log.E(err), log.Any("objType", objType), log.Any("objID", objID))
return
}
switch objType {
case cmtmod.OTypeVideo:
fakeDelta := 0
videoInfo, err := vidmod.GetVideoInfo(objID.Hex())
if err == nil && !videoInfo.ID.IsZero() {
fakeDelta = videoInfo.FakeCommentCount - videoInfo.CommentCount
if fakeDelta < 0 {
fakeDelta = 0
}
}
_, err = vidmod.UpdateOneByID(objID, bson.M{
"commentCount": int(count),
"fakeCommentCount": int(count) + fakeDelta,
})
if err != nil {
log.Error("web service comment rebuildSingleObjCommentCount vidmod.UpdateOneByID fail",
log.E(err), log.Any("objID", objID), log.Any("count", count))
}
case cmtmod.OTypeCartoon:
_, err = mediacontentmod.UpdateOneByID(objID, bson.M{"countComment": count})
if err != nil {
log.Error("web service comment rebuildSingleObjCommentCount mediacontentmod.UpdateOneByID fail",
log.E(err), log.Any("objID", objID), log.Any("count", count))
}
_, err = mediamod.UpdateByID(nil, objID, map[string]interface{}{"countComment": count})
if err != nil {
log.Error("web service comment rebuildSingleObjCommentCount mediamod.UpdateByID fail",
log.E(err), log.Any("objID", objID), log.Any("count", count))
}
}
}
func ComicsTranslatePull(req *cmtmod.ComicsTitlePullReq) (cmtmod.ComicsTitlePullResp, error) {
resp := cmtmod.ComicsTitlePullResp{List: make([]cmtmod.ComicsTitleInfo, 0)}
if req == nil {
return resp, nil
}
opts := options.Find()
pageNum := req.PageNum
if pageNum <= 0 {
pageNum = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 20
}
opts.SetSkip((pageNum - 1) * pageSize)
opts.SetLimit(pageSize)
opts.SetSort(bson.D{{Key: "_id", Value: -1}})
filter := bson.M{}
if strings.TrimSpace(req.TagId) != "" {
tagID, err := primitive.ObjectIDFromHex(strings.TrimSpace(req.TagId))
if err != nil {
return resp, err
}
filter["tags"] = tagID
}
list, err := mediamod.QueryAllList(filter, opts)
if err != nil {
return resp, err
}
for _, one := range list {
resp.List = append(resp.List, cmtmod.ComicsTitleInfo{Id: one.ID.Hex(), Title: one.Title})
}
return resp, nil
}
func ComicsTranslatePush(req *cmtmod.ComicsTitlePushReq) (cmtmod.ComicsTitlePushRes, error) {
resp := cmtmod.ComicsTitlePushRes{}
if req == nil {
return resp, nil
}
var tagID primitive.ObjectID
var hasTag bool
if strings.TrimSpace(req.TagId) != "" {
parsedTagID, err := primitive.ObjectIDFromHex(strings.TrimSpace(req.TagId))
if err != nil {
return resp, err
}
tagID = parsedTagID
hasTag = true
}
for _, one := range req.List {
mediaID, err := primitive.ObjectIDFromHex(strings.TrimSpace(one.Id))
if err != nil {
return resp, err
}
update := bson.M{"title": strings.TrimSpace(one.Title), "updateTime": time.Now()}
if hasTag {
mediaInfo, err := mediamod.GetInfo(mediaID)
if err != nil {
return resp, err
}
newTags := make([]primitive.ObjectID, 0, len(mediaInfo.Tags))
for _, tag := range mediaInfo.Tags {
if tag != tagID {
newTags = append(newTags, tag)
}
}
update["tags"] = newTags
}
if _, err = mediamod.UpdateByID(nil, mediaID, update); err != nil {
return resp, err
}
}
return resp, nil
}
+104
View File
@@ -0,0 +1,104 @@
package contentlibser
import (
"time"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/chatrobotmod"
"91porn-server/models/v/contentlibmod"
"91porn-server/web/webg"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
const RobotContentSet = "robot_content" // 机器人聊天内容集合
// 获取内容列表
func GetContentList(param contentlibmod.ReqContentList) (code stderr.Code, data interface{}, err error) {
var skip = int64((param.PageNumber - 1) * param.PageSize)
var limit = int64(param.PageSize)
total, cList, err := contentlibmod.GetContentList(param.RobotType, skip, limit)
if err != nil {
code = stderr.Failure
return
}
if cList == nil {
data = commod.ListResp{List: []contentlibmod.RespList{}}
} else {
list := make([]contentlibmod.RespList, len(cList))
for i, v := range cList {
list[i] = contentlibmod.RespList(v)
}
data = commod.ListResp{List: list, Total: total}
}
code = stderr.Success
return
}
// 增加机器人聊天内容
func AddChatContent(param contentlibmod.ReqContentAdd) (code stderr.Code, data interface{}, err error) {
// 检查类型是否有效
switch param.RobotType {
case chatrobotmod.Comment:
default:
code = stderr.RobotTypeInvalid
return
}
// 内容不能为空
if param.Content == "" {
code = stderr.RobotContentIsNull
return
}
var doc = contentlibmod.ContentLibrary{
RobotType: param.RobotType,
Content: param.Content,
CreatedAt: time.Now(),
}
// 添加到redis
if _, err = webg.Redis.SAdd(RobotContentSet, param.Content); err != nil {
code = stderr.Failure
return
}
if err = contentlibmod.Insert(doc); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
// 删除内容
func DeleteChatContent(param contentlibmod.ReqContentDel) (code stderr.Code, data interface{}, err error) {
if param.IDS == nil || len(param.IDS) == 0 {
code = stderr.RobotContentDelParamIsNull
return
}
// 获取内容
cList, err := contentlibmod.GetContentListByIDS(param.IDS)
if err != nil {
code = stderr.Failure
return
}
if len(cList) == 0 {
code = stderr.Failure
return
}
contents := make([]string, len(cList))
for i, v := range cList {
contents[i] = v.Content
}
// 从redis中移除
if _, err = webg.Redis.SRem(RobotContentSet, contents); err != nil {
code = stderr.Failure
return
}
if err = contentlibmod.DeleteByIDS(param.IDS); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
+80
View File
@@ -0,0 +1,80 @@
package contentreviewser
import (
"fmt"
"91porn-server/models/v/contentreviewmod"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ApplyResolution 把命中记录里管理员提交的修改写回到对应数据源
// 按 TargetType 分发:
//
// VIDEO → vidmod.UpdateForReview(title, content, richText)
// ACG_MEDIA → mediamod.UpdateForReview(title, summary)
// ACG_CONTENT → mediacontentmod.UpdateForReview(name, text)
//
// 该函数不修改 issue 自身的 resolveStatus;调用方在写回成功后再调 ApproveIssue
func ApplyResolution(issue *contentreviewmod.ReviewIssue) error {
switch issue.TargetType {
case contentreviewmod.IssueTargetVideo:
return vidmod.UpdateForReview(
issue.TargetID,
issue.SubmittedTitle,
issue.SubmittedContent,
issue.SubmittedRichText,
)
case contentreviewmod.IssueTargetACGMedia:
return mediamod.UpdateForReview(
issue.TargetID,
issue.SubmittedTitle,
issue.SubmittedContent,
)
case contentreviewmod.IssueTargetACGContent:
return mediacontentmod.UpdateForReview(
issue.TargetID,
issue.SubmittedTitle,
issue.SubmittedContent,
)
default:
return fmt.Errorf("unsupported targetType: %s", issue.TargetType)
}
}
// ApplyOffShelfBatch 按 TargetType 分组把命中目标批量下架:
//
// VIDEO → vidmod.OffShelfManyForReview(TargetIDs) 视频置为 OffShelf(5)
// ACG_MEDIA → mediamod.OffShelfManyForReview(TargetIDs) Media 置为 status=0
// ACG_CONTENT → mediacontentmod.OffShelfManyForReview(TargetIDs) 章节置为 isActive=false
//
// 每个类型一次 UpdateMany;遇到未知 TargetType 直接返回错误,整批回滚交给调用方语义处理。
// 该函数不修改 issue 自身 resolveStatus;调用方下架成功后再调 BatchOffShelfResolveIssues
func ApplyOffShelfBatch(issues []*contentreviewmod.ReviewIssue) error {
var videoIDs, mediaIDs, contentIDs []primitive.ObjectID
for _, iss := range issues {
switch iss.TargetType {
case contentreviewmod.IssueTargetVideo:
videoIDs = append(videoIDs, iss.TargetID)
case contentreviewmod.IssueTargetACGMedia:
mediaIDs = append(mediaIDs, iss.TargetID)
case contentreviewmod.IssueTargetACGContent:
contentIDs = append(contentIDs, iss.TargetID)
default:
return fmt.Errorf("unsupported targetType: %s (issueId=%s)", iss.TargetType, iss.ID.Hex())
}
}
if err := vidmod.OffShelfManyForReview(videoIDs); err != nil {
return fmt.Errorf("offshelf videos fail: %w", err)
}
if err := mediamod.OffShelfManyForReview(mediaIDs); err != nil {
return fmt.Errorf("offshelf media fail: %w", err)
}
if err := mediacontentmod.OffShelfManyForReview(contentIDs); err != nil {
return fmt.Errorf("offshelf media content fail: %w", err)
}
return nil
}
+49
View File
@@ -0,0 +1,49 @@
package currencyser
import (
"91porn-server/common/stderr"
"91porn-server/models/v/currencymod"
)
// Add 新增裸聊资料
func Add(in *AddCond) stderr.Code {
// 插入数据库
n, err := in.Document()
if err != nil {
return stderr.ErrReqForbidden
}
if err := currencymod.Add(n); err != nil {
return stderr.ErrDbInsertError
}
return stderr.Success
}
// Edit 编辑裸聊资料
func Edit(in *EditCond) stderr.Code {
if err := currencymod.Edit(in.Cond(), in.Update()); err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// QueryAll 查询裸聊列表
func QueryAll(in *QueryAllCond) (interface{}, stderr.Code) {
var data = map[string]interface{}{
"data": []interface{}{},
"total": 0,
}
count, err := currencymod.QueryCount(in.Query())
if err != nil {
return data, stderr.ErrDbQueryError
}
if count == 0 {
return data, stderr.Success
}
data["total"] = count
list, err := currencymod.QueryAll(in.Query(), in.Options())
if err != nil {
return data, stderr.ErrDbQueryError
}
data["data"] = list
return data, stderr.Success
}
+135
View File
@@ -0,0 +1,135 @@
package currencyser
import (
"encoding/json"
"fmt"
"time"
"91porn-server/common/log"
"91porn-server/models/commod"
"91porn-server/models/v/currencymod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
type AddCond struct {
Name string `json:"name"` //货币名称
Type commod.CurrencyType `json:"type"` //货币类型
Coins int64 `json:"coins"` //购买货币数
Price int64 `json:"price"` //价格 单位:元
CouponDesc string `json:"couponDesc"` //优惠描述
GiveVipDays int `json:"giveVipDays"` //赠送vip天数
LouFengUnlockTimes int `json:"louFengUnlockTimes"` //赠送楼凤解锁次数
GiveGameCoin int64 `json:"giveGameCoin"` //赠送游戏金币
GiveFruitCoin int64 `json:"giveFruitCoin"` //赠送果币
GiveGold int64 `json:"giveGold"` //赠送金币
FirstGiveVipDays int `json:"firstGiveVipDays"` //首次赠送vip天数
FirstGiveGameCoin int64 `json:"firstGiveGameCoin"` //首次赠送游戏金币
NotFirst bool `json:"notFirst"` //非首充依然赠送
Active bool `json:"active"` //是否激活
}
func (cond *AddCond) Document() (interface{}, error) {
data, err := json.Marshal(&cond)
if err != nil {
log.Error(fmt.Sprintf("新增裸聊json序列化异常[%v]", err))
return nil, err
}
var n currencymod.Currency
if err = json.Unmarshal(data, &n); err != nil {
log.Error(fmt.Sprintf("新增裸聊json反序列化异常[%v]", err))
return nil, err
}
n.CreatedAt = time.Now()
n.UpdatedAt = time.Now()
return n, nil
}
type EditCond struct {
ID primitive.ObjectID `json:"id" binding:"required"` //裸聊ID
Name *string `json:"name"` //货币名称
Type *commod.CurrencyType `json:"type"` //货币类型
Coins *int64 `json:"coins"` //购买货币数
Price *int64 `json:"price"` //价格 单位:元
CouponDesc *string `json:"couponDesc"` //优惠描述
GiveVipDays *int `json:"giveVipDays"` //赠送vip天数
LouFengUnlockTimes *int `json:"louFengUnlockTimes"` //赠送楼凤解锁次数
GiveGold *int64 `json:"giveGold"` //赠送金币
GiveGameCoin *int64 `json:"giveGameCoin"` //赠送游戏金币
GiveFruitCoin *int64 `json:"giveFruitCoin"` //赠送果币
NotFirst *bool `json:"notFirst"` //非首充依然赠送
IsActive *bool `json:"isActive"` //是否激活
FirstGiveVipDays *int `json:"firstGiveVipDays" bson:"firstGiveVipDays"` //首次赠送vip天数
FirstGiveGameCoin *int64 `json:"firstGiveGameCoin" bson:"firstGiveGameCoin"` //首次赠送游戏金币
}
func (cond *EditCond) Cond() primitive.M {
return bson.M{"_id": cond.ID}
}
func (cond *EditCond) Update() primitive.M {
var update = bson.M{}
if cond.Name != nil {
update["name"] = cond.Name
}
if cond.Type != nil {
update["type"] = cond.Type
}
if cond.Coins != nil {
update["coins"] = cond.Coins
}
if cond.Price != nil {
update["price"] = cond.Price
}
if cond.CouponDesc != nil {
update["couponDesc"] = cond.CouponDesc
}
if cond.GiveVipDays != nil {
update["giveVipDays"] = cond.GiveVipDays
}
if cond.LouFengUnlockTimes != nil {
update["louFengUnlockTimes"] = cond.LouFengUnlockTimes
}
if cond.GiveGold != nil {
update["giveGold"] = cond.GiveGold
}
if cond.GiveGameCoin != nil {
update["giveGameCoin"] = cond.GiveGameCoin
}
if cond.GiveFruitCoin != nil {
update["giveFruitCoin"] = cond.GiveFruitCoin
}
if cond.NotFirst != nil {
update["notFirst"] = cond.NotFirst
}
if cond.IsActive != nil {
update["isActive"] = cond.IsActive
}
if cond.FirstGiveVipDays != nil {
update["firstGiveVipDays"] = cond.FirstGiveVipDays
}
if cond.FirstGiveGameCoin != nil {
update["firstGiveGameCoin"] = cond.FirstGiveGameCoin
}
return bson.M{"$set": update}
}
type QueryAllCond struct {
IsActive *bool `form:"isActive"` //是否激活
Type commod.CurrencyType `form:"type" binding:"required"` //货币类型
Page commod.Page
}
func (cond *QueryAllCond) Query() primitive.M {
var query = bson.M{"type": cond.Type}
if cond.IsActive != nil {
query["isActive"] = cond.IsActive
}
return query
}
func (cond *QueryAllCond) Options() *options.FindOptions {
return options.Find().SetLimit(int64(cond.Page.Limit())).SetSkip(int64(cond.Page.Skip())).SetSort(bson.D{{Key: "coins", Value: 1}})
}
+72
View File
@@ -0,0 +1,72 @@
package daichongser
import (
"context"
"time"
"91porn-server/common/daichong"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/rchgordmod"
"91porn-server/web/webg"
)
const (
rate_yinse_coin = 10 //金额转换成金币的单位 1元=10音色币
rate_yuan_feng = 100 //将元转换为分
retries = 7 //通知7次
)
var interval = []int64{0, 60, 5 * 60, 10 * 60, 30 * 60, 60 * 60, 2 * 60 * 60} //通知时间间隔
// 上分通知 将订单的处理结果通知给代充平台
func OrderCallBack(ctx context.Context, req daichong.OrderCallSign) (commod.Resp, stderr.Code) {
sign, err := daichong.Convert2Sign(req, webg.Conf.DaiChong.AppSecret)
if err != nil {
log.ErrorX(ctx, "create sign data wrong", log.Any("warn", err))
return commod.Resp{}, stderr.Failure
}
orderReq := daichong.CommonReq{
AppId: webg.Conf.DaiChong.AppID,
Data: sign,
}
resp, err := daichong.OrderCallBack(ctx, orderReq, webg.Conf.DaiChong.Domain)
if err != nil {
log.ErrorX(ctx, "GetOrderInfo send http wrong", log.Any("warn", err))
return resp, stderr.Failure
}
return resp, resp.Code
}
// 通知第三方回调的结果 按照时间间隔通知
func NotifyThirdOrderCallBack(ctx context.Context, oid string, callBackTime int64, orderStatus int) {
var cnt = 0
selector := rchgordmod.ReplaceEditSelector{}
for cnt < retries {
order, _ := rchgordmod.FindRechargeOrderByThirdOrderId(oid)
if order.NotifyStatus == rchgordmod.Notify_SUCCESS {
return
}
time.Sleep(time.Duration(interval[cnt]) * time.Second)
resp, code := OrderCallBack(ctx, daichong.OrderCallSign{
OrderId: oid,
Time: callBackTime,
Status: orderStatus,
})
if code == stderr.Success {
notifySuccess := rchgordmod.Notify_SUCCESS
selector.NotifyStatus = &notifySuccess
_ = rchgordmod.UpdateBaseOnOidAndStatus(nil, oid, order.Status, selector)
log.Info("daichong order call back send http request successfully", log.Any("msg", resp.Msg), log.Any("orderstatus", orderStatus),
log.Any("orderId", oid),
log.Any("time", time.Now().Format(time.RFC3339)))
return
}
notifyFailure := rchgordmod.Notify_FAILURE
selector.NotifyStatus = &notifyFailure
_ = rchgordmod.UpdateBaseOnOidAndStatus(nil, oid, order.Status, selector)
log.Warn("daichong order call back send http request failed", log.Any("responseCode", code), log.Any("msg", resp.Msg))
cnt++
}
}
+44
View File
@@ -0,0 +1,44 @@
package deduction
import (
"91porn-server/models/l/payvidlgmod"
"91porn-server/models/v/usermod"
)
func StatPublisherDeduction(arg *Query) (*PublisherDeduction, error) {
res, err := payvidlgmod.PublisherVideoDeductionRange(*arg.UID, *arg.StartTime, *arg.EndTime)
if err != nil {
return nil, err
}
publisher, err := usermod.FindUserByUIDForNoCache(*arg.UID)
if err != nil {
return nil, err
}
// 避免脏数据引起的空指针
if publisher == nil {
publisher = &usermod.User{UID: *arg.UID}
}
deduction, deductionCount, totalAmount, totalCount := int64(0), int64(0), int64(0), int64(0)
for _, stat := range res {
if stat.IsVideoDeduction {
deduction = stat.TotalAmount
deductionCount = stat.VideoCount
}
totalAmount += stat.TotalAmount
totalCount += stat.VideoCount
}
ret := PublisherDeduction{
PublisherInfo: PublisherInfo{
UID: publisher.UID,
Name: publisher.Name,
VideoDeduction: publisher.VideoDeduction,
VideoDeducitonCount: publisher.VideoDeductionCount,
VideoDeductionPayCount: publisher.VideoDeductionPayCount,
},
AfterDeduction: totalAmount - deduction,
BeforeDeduction: totalAmount,
DeductionCount: deductionCount,
NonDeductionCount: totalCount - deductionCount,
}
return &ret, err
}
+27
View File
@@ -0,0 +1,27 @@
package deduction
import "time"
type Query struct {
StartTime *time.Time `form:"startTime" json:"startTime,omitempty" binding:"required"` //开始时间
EndTime *time.Time `form:"endTime" json:"endTime,omitempty" binding:"required"` //结束时间
UID *uint64 `form:"uid" json:"uid" binding:"required"` //uid
}
// 视频博主信息
type PublisherInfo struct {
UID uint64 `json:"uid"` // 视频博主id
Name string `json:"name"` // 博主姓名
VideoDeduction float64 `json:"videoDeduction"` // 视频扣量
VideoDeducitonCount int `json:"videoDeductionCount"` // 已扣量视频数
VideoDeductionPayCount int `json:"videoDeductionPayCount"` // 视频售卖总次数
}
// 视频扣量统计
type PublisherDeduction struct {
PublisherInfo `json:",inline"`
BeforeDeduction int64 `json:"beforeDeduction"` // 扣量前总售卖(金币)
AfterDeduction int64 `json:"afterDeduction"` // 扣量后总售卖(金币)
DeductionCount int64 `json:"deductionCount"` // 被扣量视频总数
NonDeductionCount int64 `json:"nonDedcutionCount"` // 未扣量视频总数
}
@@ -0,0 +1,236 @@
package discount_area_ser
import (
"91porn-server/common"
"91porn-server/common/cachev2"
"91porn-server/common/constant/redisconst"
sli "91porn-server/common/slice"
"91porn-server/common/stderr"
"91porn-server/models/v/discount_area_mod"
"91porn-server/models/v/discount_area_video_mod"
"91porn-server/models/v/vidmod"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
func AddDiscountArea(req *discount_area_mod.WebDiscountAreaAddReq) (err error) {
now := time.Now()
model := &discount_area_mod.DiscountArea{
Title: req.Title,
Desc: req.Desc,
Discount: int(req.Discount),
Status: int(req.Status),
SortCode: int(req.SortCode),
RecommendNum: int(req.RecommendNum),
UpdatedAt: now,
CreatedAt: now,
}
//最多10个
if model.RecommendNum > 10 {
model.RecommendNum = 10
}
err = discount_area_mod.AddDiscountArea(model)
if err != nil {
return
}
// 删除缓存
cachev2.Classes().AutoListKey(redisconst.DiscountArea).AutoClear(true, nil)
return
}
func UpdateDiscountArea(req *discount_area_mod.WebDiscountAreaUpdateReq) (err error) {
var maxRecomment uint = 10
if req.RecommendNum != nil && *req.RecommendNum > 10 {
req.RecommendNum = &maxRecomment
}
objId, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return
}
update, err := common.ToBsonM(req.WebDiscountAreaUpdateData)
if err != nil {
return
}
update["updateAt"] = time.Now()
err = discount_area_mod.UpdateDiscountArea(objId, update)
if err != nil {
return
}
// 删除缓存
cachev2.Classes().AutoListKey(redisconst.DiscountArea).AutoClear(true, nil)
return
}
// DeleteDiscountArea 删除折扣专区
func DeleteDiscountArea(id string) (err error) {
objId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return
}
update := bson.M{
"status": 2,
"updateAt": time.Now(),
}
err = discount_area_mod.UpdateDiscountArea(objId, update)
if err != nil {
return
}
// 删除缓存
cachev2.Classes().AutoListKey(redisconst.DiscountArea).AutoClear(true, nil)
return
}
// GetDiscountAreaVids 获取专区下的视频
func GetDiscountAreaVids(req *discount_area_mod.WebDiscountAreaVidsReq) (list []discount_area_mod.WebDiscountAreaVideo, total int64, err error) {
filter := bson.M{}
if req.DiscountAreaId != "" {
discountAreaId, _ := primitive.ObjectIDFromHex(req.DiscountAreaId)
filter["discountAreaId"] = discountAreaId
}
if req.Vid != "" {
videoID, _ := primitive.ObjectIDFromHex(req.Vid)
filter["videoID"] = videoID
}
opts := options.Find().SetSort(bson.D{{Key: "createdAt", Value: -1}}).SetSkip(int64(req.Skip())).SetLimit(int64(req.Limit()))
discountAreaVideoList, total, err := discount_area_video_mod.GetListByCond(filter, opts)
if err != nil {
return
}
vids := []primitive.ObjectID{}
discountAreaIds := []primitive.ObjectID{}
// 获取视频信息
for _, v := range discountAreaVideoList {
vids = append(vids, v.VideoID)
discountAreaIds = append(discountAreaIds, v.DiscountAreaId)
}
videoList, err := vidmod.GetVideoListByIDsNoStatus(sli.RemoveRepObjectID(vids))
if err != nil {
return
}
videoMap := make(map[primitive.ObjectID]vidmod.VideoModel)
for _, video := range videoList {
videoMap[video.ID] = *video
}
discountAreaList, err := discount_area_mod.GetNoStatusDiscountAreaByIds(sli.RemoveRepObjectID(discountAreaIds))
if err != nil {
return
}
discountAreaMap := make(map[primitive.ObjectID]discount_area_mod.DiscountArea)
for _, discountArea := range discountAreaList {
discountAreaMap[discountArea.ID] = discountArea
}
for _, v := range discountAreaVideoList {
discountAreaVideo := discount_area_mod.WebDiscountAreaVideo{
Id: v.ID,
VideoID: v.VideoID,
DiscountAreaId: v.DiscountAreaId,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
}
if video, ok := videoMap[v.VideoID]; ok {
discountAreaVideo.Title = video.Title
discountAreaVideo.Cover = video.Cover
discountAreaVideo.PublisherID = video.PublisherID
discountAreaVideo.OriginalPrice = video.Coins
}
if discountArea, ok := discountAreaMap[v.DiscountAreaId]; ok {
discountAreaVideo.DiscountAreaTitle = discountArea.Title
discountAreaVideo.DiscountedPrice = decimal.NewFromInt(int64(discountArea.Discount)).Shift(-2).
Mul(decimal.NewFromInt(discountAreaVideo.OriginalPrice)).Round(0).IntPart()
}
list = append(list, discountAreaVideo)
}
return
}
// DiscountAreaAddVid 折扣区新增视频
func DiscountAreaAddVid(req *discount_area_mod.WebDiscountAreaAddVidReq) (code stderr.Code) {
if req.DiscountAreaId.IsZero() || len(req.VIds) == 0 {
return stderr.ErrParamError
}
// 判断是否是视频
videoList, err := vidmod.GetVideoListByIDs(req.VIds)
if err != nil {
return stderr.ErrDbQueryError
}
vids := []primitive.ObjectID{}
for _, v := range videoList {
// 不是视频,直接返回错误
if v.NewsType != vidmod.SP {
return stderr.NotAVideo
}
vids = append(vids, v.ID)
}
if len(vids) == 0 {
return
}
// 查询是否已经在其他折扣区,是的话则直接报错
list, err := discount_area_video_mod.GetDiscountAreaVideoByVids(vids)
if err != nil {
return stderr.ErrDbQueryError
}
if len(list) > 0 {
// 不能一个视频同时在两个折扣区
return stderr.VideoInDiscountArea
}
now := time.Now()
batchAddVids := []discount_area_video_mod.DiscountAreaVideo{}
for _, vid := range vids {
batchAddVids = append(batchAddVids, discount_area_video_mod.DiscountAreaVideo{
DiscountAreaId: req.DiscountAreaId,
VideoID: vid,
CreatedAt: now,
UpdatedAt: now,
})
}
// 写入折扣区
err = discount_area_video_mod.BatchAdd(batchAddVids)
if err != nil {
return stderr.ErrDbInsertManyError
}
update := bson.M{}
update["discountAreaId"] = req.DiscountAreaId
update["updatedAt"] = time.Now()
_, err = vidmod.UpdateManyVideo(vids, update)
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
// DiscountAreaDeleteVid 移除折扣区视频
func DiscountAreaDeleteVid(ids []primitive.ObjectID) (code stderr.Code) {
if len(ids) == 0 {
return stderr.ErrParamError
}
list, err := discount_area_video_mod.GetDiscountAreaVideoByIds(ids)
if err != nil {
return stderr.ErrDbQueryError
}
vids := []primitive.ObjectID{}
for _, v := range list {
vids = append(vids, v.VideoID)
}
// 删除视频
err = discount_area_video_mod.DeleteDiscountAreaVideoByIds(ids)
if err != nil {
return stderr.ErrDbDeleteError
}
// 移除
update := bson.M{}
update["discountAreaId"] = nil
update["updatedAt"] = time.Now()
_, err = vidmod.UpdateManyVideo(vids, update)
if err != nil {
return stderr.ErrDbUpdateError
}
return stderr.Success
}
+492
View File
@@ -0,0 +1,492 @@
package dramatopicser
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"91porn-server/common/db"
"91porn-server/common/dramatopic"
"91porn-server/models/cache/sysconfdata"
"91porn-server/models/commod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/modulesectionmod"
"91porn-server/models/v/sysconfmod"
"91porn-server/web/webg"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ListRequest struct {
Name string `form:"name" json:"name"`
TopicType string `form:"topicType" json:"topicType" binding:"omitempty,oneof=SYSTEM CUSTOM"`
Status *int `form:"status" json:"status" binding:"omitempty,oneof=0 1"`
commod.Page
}
type Topic struct {
TopicID string `json:"topicId"`
Name string `json:"name"`
TopicType string `json:"topicType"`
SystemKey string `json:"systemKey"`
Status int `json:"status"`
Sort int `json:"sort"`
WorkCount int64 `json:"workCount"`
Editable bool `json:"editable"`
WorksEditable bool `json:"worksEditable"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
systemOrder int
}
type ListResponse struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []Topic `json:"list"`
}
func (req ListRequest) List(now time.Time) (ListResponse, error) {
moduleID, ok, err := dramatopic.ModuleID(now)
if err != nil {
return ListResponse{}, err
}
sections := make([]modulesectionmod.Section, 0)
if ok {
sections, err = modulesectionmod.ListBySubModule(moduleID, 1000)
if err != nil {
return ListResponse{}, err
}
}
sectionIDs := make([]primitive.ObjectID, 0, len(sections))
for _, section := range sections {
sectionIDs = append(sectionIDs, section.ID)
}
counts, err := mediamod.CountActiveDramaBySectionIDs(sectionIDs)
if err != nil {
return ListResponse{}, err
}
activeTotal, err := mediamod.QueryAllCount(bson.M{"mediaType": mediamod.MediaTypeDrama, "status": 1, "isDelete": false})
if err != nil {
return ListResponse{}, err
}
all := make([]Topic, 0, 3+len(sections))
if req.TopicType == "" || req.TopicType == dramatopic.TypeSystem {
for _, system := range dramatopic.SystemTopics() {
if !matches(req, system.Name, 1) {
continue
}
all = append(all, Topic{
TopicID: system.ID, Name: system.Name, TopicType: dramatopic.TypeSystem,
SystemKey: system.SystemKey, Status: 1, Sort: system.Sort, WorkCount: activeTotal,
Editable: false, WorksEditable: false, systemOrder: system.TieOrder,
})
}
}
if req.TopicType == "" || req.TopicType == dramatopic.TypeCustom {
for _, section := range sections {
status := int(valueOrZeroUint8(section.Status))
if !matches(req, section.SectionName, status) {
continue
}
createdAt := section.CreatedAt
updatedAt := section.UpdatedAt
all = append(all, Topic{
TopicID: section.ID.Hex(), Name: section.SectionName, TopicType: dramatopic.TypeCustom,
Status: status, Sort: valueOrZeroInt(section.Sort), WorkCount: counts[section.ID],
Editable: true, WorksEditable: true, CreatedAt: &createdAt, UpdatedAt: &updatedAt,
})
}
}
sort.SliceStable(all, func(i, j int) bool {
if all[i].Sort != all[j].Sort {
return all[i].Sort > all[j].Sort
}
if all[i].systemOrder != all[j].systemOrder {
return all[i].systemOrder > all[j].systemOrder
}
if all[i].UpdatedAt == nil {
return false
}
if all[j].UpdatedAt == nil {
return true
}
return all[i].UpdatedAt.After(*all[j].UpdatedAt)
})
response := ListResponse{Total: int64(len(all)), List: []Topic{}}
start := int(req.Skip64())
if start >= len(all) {
return response, nil
}
end := start + int(req.PageSize)
if end > len(all) {
end = len(all)
}
response.HasNext = end < len(all)
response.List = all[start:end]
return response, nil
}
func matches(req ListRequest, name string, status int) bool {
if req.Status != nil && *req.Status != status {
return false
}
return req.Name == "" || strings.Contains(strings.ToLower(name), strings.ToLower(strings.TrimSpace(req.Name)))
}
type CreateRequest struct {
Name string `json:"name" binding:"required"`
Status *int `json:"status" binding:"omitempty,oneof=0 1"`
Sort *int `json:"sort" binding:"omitempty,min=0"`
}
func (req CreateRequest) Create(now time.Time) (primitive.ObjectID, error) {
name, err := validateName(req.Name)
if err != nil {
return primitive.NilObjectID, err
}
moduleID, ok, err := dramatopic.ModuleID(now)
if err != nil || !ok {
if err == nil {
err = errors.New("短剧专题模块未配置")
}
return primitive.NilObjectID, err
}
status := uint8(1)
if req.Status != nil {
status = uint8(*req.Status)
}
topicSort := 0
if req.Sort != nil {
topicSort = *req.Sort
}
id := primitive.NewObjectID()
err = modulesectionmod.InsertOne(modulesectionmod.Section{
ID: id, SectionName: name, SubModuleID: moduleID, Status: &status, Sort: &topicSort,
})
return id, err
}
type UpdateRequest struct {
TopicID string `json:"topicId" binding:"required"`
Name *string `json:"name"`
Status *int `json:"status" binding:"omitempty,oneof=0 1"`
Sort *int `json:"sort" binding:"omitempty,min=0"`
}
func (req UpdateRequest) Update(now time.Time) error {
topicID, moduleID, _, err := customTopic(req.TopicID, now)
if err != nil {
return err
}
fields := bson.M{}
if req.Name != nil {
name, nameErr := validateName(*req.Name)
if nameErr != nil {
return nameErr
}
fields["sectionName"] = name
}
if req.Status != nil {
fields["status"] = uint8(*req.Status)
}
if req.Sort != nil {
fields["sort"] = *req.Sort
}
if len(fields) == 0 {
return errors.New("至少需要修改一个字段")
}
return webg.VideoDB.Trans(func(t *db.MongoTool) error {
matched, updateErr := modulesectionmod.UpdateTopicFields(t, topicID, moduleID, fields)
if updateErr != nil {
return updateErr
}
if matched == 0 {
return errors.New("专题不存在")
}
if name, renamed := fields["sectionName"]; renamed {
if _, updateErr = mediamod.UpdateMany(t, bson.M{"sId": topicID}, bson.M{"$set": bson.M{"sectionName": name}}); updateErr != nil {
return updateErr
}
}
return nil
})
}
type DeleteRequest struct {
TopicID string `json:"topicId" binding:"required"`
}
func (req DeleteRequest) Delete(now time.Time) error {
topicID, moduleID, _, err := customTopic(req.TopicID, now)
if err != nil {
return err
}
return webg.VideoDB.Trans(func(t *db.MongoTool) error {
if _, updateErr := mediamod.UpdateMany(t, bson.M{"sId": topicID}, bson.M{"$set": bson.M{
"sId": primitive.NilObjectID, "sectionName": "", "sectionSort": 0,
}}); updateErr != nil {
return updateErr
}
deleted, deleteErr := modulesectionmod.DeleteTopic(t, topicID, moduleID)
if deleteErr != nil {
return deleteErr
}
if deleted != 1 {
return errors.New("自定义专题不存在")
}
return nil
})
}
type SortRequest struct {
Items []SortRequestItem `json:"items" binding:"required,min=1,max=1000,dive"`
}
type SortRequestItem struct {
TopicID string `json:"topicId" binding:"required"`
Sort int `json:"sort" binding:"min=0"`
}
func (req SortRequest) Update(now time.Time) error {
moduleID, moduleOK, err := dramatopic.ModuleID(now)
if err != nil {
return err
}
type target struct {
item SortRequestItem
system *dramatopic.SystemTopic
custom primitive.ObjectID
}
targets := make([]target, 0, len(req.Items))
seen := make(map[string]struct{}, len(req.Items))
systemTopics := make(map[string]dramatopic.SystemTopic, 3)
for _, system := range dramatopic.SystemTopics() {
systemTopics[system.ID] = system
}
for _, item := range req.Items {
if _, exists := seen[item.TopicID]; exists {
return errors.New("topicId不能重复")
}
seen[item.TopicID] = struct{}{}
if system, found := systemTopics[item.TopicID]; found {
topic := system
targets = append(targets, target{item: item, system: &topic})
continue
}
if !moduleOK {
return errors.New("短剧专题模块未配置")
}
id, parseErr := primitive.ObjectIDFromHex(item.TopicID)
if parseErr != nil {
return errors.New("无效的专题ID")
}
section, findErr := modulesectionmod.GetBySectionByID(id)
if findErr != nil || section.SubModuleID != moduleID || section.DeletedAt != nil {
return errors.New("自定义专题不存在")
}
targets = append(targets, target{item: item, custom: id})
}
sysconfmod.EnsureInitData()
for _, target := range targets {
if target.system != nil {
config, configErr := sysconfmod.GetByVCode(target.system.VCode)
if configErr != nil {
return configErr
}
if _, configErr = sysconfdata.UpdateData(nil, config.ID.Hex(), map[string]interface{}{
"value": strconv.Itoa(target.item.Sort), "updatedAt": time.Now(),
}); configErr != nil {
return configErr
}
continue
}
if _, updateErr := modulesectionmod.UpdateTopicFields(nil, target.custom, moduleID, bson.M{"sort": target.item.Sort}); updateErr != nil {
return updateErr
}
}
return nil
}
type UpdateWorksRequest struct {
TopicID string `json:"topicId" binding:"required"`
WorkIDs []string `json:"workIds" binding:"max=500,dive,required"`
}
type UpdateWorksResponse struct {
TopicID string `json:"topicId"`
WorkCount int `json:"workCount"`
}
type WorkConflict struct {
MediaID string `json:"mediaId"`
TopicID string `json:"topicId"`
TopicName string `json:"topicName"`
}
type WorksConflictError struct {
Message string `json:"message"`
Conflicts []WorkConflict `json:"conflicts"`
}
func (e *WorksConflictError) Error() string { return e.Message }
type topicWorkAssignment struct {
ID primitive.ObjectID
Sort int
}
func appendTopicWorkAssignments(
topicID primitive.ObjectID,
ids []primitive.ObjectID,
mediaMap map[primitive.ObjectID]mediamod.Media,
currentCount int64,
) ([]topicWorkAssignment, int) {
newIDs := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
if mediaMap[id].SID != topicID {
newIDs = append(newIDs, id)
}
}
total := int(currentCount) + len(newIDs)
assignments := make([]topicWorkAssignment, 0, len(newIDs))
for i, id := range newIDs {
assignments = append(assignments, topicWorkAssignment{ID: id, Sort: total - i})
}
return assignments, total
}
func (req UpdateWorksRequest) Update(now time.Time) (UpdateWorksResponse, error) {
topicID, _, section, err := customTopic(req.TopicID, now)
if err != nil {
return UpdateWorksResponse{}, err
}
ids := make([]primitive.ObjectID, 0, len(req.WorkIDs))
seen := make(map[primitive.ObjectID]struct{}, len(req.WorkIDs))
for _, raw := range req.WorkIDs {
id, parseErr := primitive.ObjectIDFromHex(raw)
if parseErr != nil {
return UpdateWorksResponse{}, errors.New("workIds包含无效ID")
}
if _, exists := seen[id]; exists {
return UpdateWorksResponse{}, errors.New("workIds不能重复")
}
seen[id] = struct{}{}
ids = append(ids, id)
}
medias, mediaMap, err := mediamod.GetListByIds(ids)
if err != nil {
return UpdateWorksResponse{}, err
}
if len(medias) != len(ids) {
return UpdateWorksResponse{}, errors.New("部分短剧不存在")
}
conflictTopicIDs := make([]primitive.ObjectID, 0)
for _, id := range ids {
media := mediaMap[id]
if media.MediaType != mediamod.MediaTypeDrama || media.IsDelete {
return UpdateWorksResponse{}, fmt.Errorf("作品%s不是有效短剧", id.Hex())
}
if !media.SID.IsZero() && media.SID != topicID {
conflictTopicIDs = append(conflictTopicIDs, media.SID)
}
}
if len(conflictTopicIDs) > 0 {
sections, _ := modulesectionmod.GetBySectionByIDs(conflictTopicIDs)
names := make(map[primitive.ObjectID]string, len(sections))
for _, item := range sections {
names[item.ID] = item.SectionName
}
conflict := &WorksConflictError{Message: "部分短剧已配置到其他专题", Conflicts: []WorkConflict{}}
for _, id := range ids {
media := mediaMap[id]
if !media.SID.IsZero() && media.SID != topicID {
conflict.Conflicts = append(conflict.Conflicts, WorkConflict{MediaID: id.Hex(), TopicID: media.SID.Hex(), TopicName: names[media.SID]})
}
}
return UpdateWorksResponse{}, conflict
}
currentCount, err := mediamod.QueryAllCount(bson.M{
"sId": topicID, "mediaType": mediamod.MediaTypeDrama, "isDelete": false,
})
if err != nil {
return UpdateWorksResponse{}, err
}
// 作品配置是增量追加;已在当前专题内的作品保持原顺序,避免单个追加覆盖整批历史配置。
assignments, total := appendTopicWorkAssignments(topicID, ids, mediaMap, currentCount)
if len(assignments) == 0 {
return UpdateWorksResponse{TopicID: req.TopicID, WorkCount: total}, nil
}
err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
for _, assignment := range assignments {
matched, updateErr := mediamod.UpdateMany(t, bson.M{
"_id": assignment.ID, "mediaType": mediamod.MediaTypeDrama, "isDelete": false,
"$or": []bson.M{{"sId": primitive.NilObjectID}, {"sId": bson.M{"$exists": false}}},
}, bson.M{"$set": bson.M{
"sId": topicID, "sectionName": section.SectionName, "sectionSort": assignment.Sort,
}})
if updateErr != nil {
return updateErr
}
if matched != 1 {
return errors.New("专题作品并发冲突,请刷新后重试")
}
}
return nil
})
if err != nil {
return UpdateWorksResponse{}, err
}
return UpdateWorksResponse{TopicID: req.TopicID, WorkCount: total}, nil
}
func customTopic(rawID string, now time.Time) (primitive.ObjectID, primitive.ObjectID, modulesectionmod.Section, error) {
if _, system := dramatopic.FindSystem(rawID); system {
return primitive.NilObjectID, primitive.NilObjectID, modulesectionmod.Section{}, errors.New("系统专题不允许此操作")
}
id, err := primitive.ObjectIDFromHex(rawID)
if err != nil {
return primitive.NilObjectID, primitive.NilObjectID, modulesectionmod.Section{}, errors.New("无效的专题ID")
}
moduleID, ok, err := dramatopic.ModuleID(now)
if err != nil || !ok {
if err == nil {
err = errors.New("短剧专题模块未配置")
}
return primitive.NilObjectID, primitive.NilObjectID, modulesectionmod.Section{}, err
}
section, err := modulesectionmod.GetBySectionByID(id)
if err != nil || section.SubModuleID != moduleID || section.DeletedAt != nil {
if err == nil {
err = errors.New("自定义专题不存在")
}
return primitive.NilObjectID, primitive.NilObjectID, modulesectionmod.Section{}, err
}
return id, moduleID, section, nil
}
func validateName(raw string) (string, error) {
name := strings.TrimSpace(raw)
if count := utf8.RuneCountInString(name); count < 1 || count > 20 {
return "", errors.New("专题名称长度必须为1到20个字符")
}
return name, nil
}
func valueOrZeroInt(value *int) int {
if value == nil {
return 0
}
return *value
}
func valueOrZeroUint8(value *uint8) uint8 {
if value == nil {
return 0
}
return *value
}
+258
View File
@@ -0,0 +1,258 @@
package exchcodeser
import (
"fmt"
"strconv"
"strings"
"time"
"91porn-server/common"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/adminmod"
"91porn-server/models/v/exchcodemod"
)
const SuperAdmin = "superAdmin"
// 获取兑换码列表
func GetExchangeCodeList(param exchcodemod.ListReqParam, manager string) (code stderr.Code, data interface{}, err error) {
var filterParams = exchcodemod.FilterDoc{
BatchNum: param.BatchNum,
Channel: param.Channel,
Code: param.Code,
App: param.App,
Authority: param.Authority,
}
// 获取管理员角色
admin, err := adminmod.FindOneByName(manager)
if err != nil || admin.ID.IsZero() {
code = stderr.AdminIDErr
return
}
var isSuperAdmin bool
if admin.Role == SuperAdmin {
isSuperAdmin = true
}
total, cList, err := exchcodemod.GetCodeList(filterParams, param.Status, param.Page, isSuperAdmin)
if err != nil {
code = stderr.Failure
return
}
list := make([]exchcodemod.RespList, len(cList))
for i, v := range cList {
tmp := exchcodemod.RespList(v)
if v.InvalidAt.Before(time.Now()) {
tmp.Status = exchcodemod.StatusExpired
}
list[i] = tmp
}
if len(list) == 0 {
data = commod.ListResp{Total: 0, List: []exchcodemod.RespList{}}
} else {
data = commod.ListResp{Total: total, List: list}
}
code = stderr.Success
return
}
// ExchangeCodes 获取兑换码列表,导出
func ExchangeCodes(param exchcodemod.ListReqParam) []exchcodemod.RespList {
var filterParams = exchcodemod.FilterDoc{
BatchNum: param.BatchNum,
Channel: param.Channel,
Code: param.Code,
App: param.App,
Authority: param.Authority,
}
var isSuperAdmin bool
if param.Role != nil && *param.Role == SuperAdmin {
isSuperAdmin = true
}
_, cList, err := exchcodemod.GetCodeList(filterParams, param.Status, param.Page, isSuperAdmin)
if err != nil {
return nil
}
list := make([]exchcodemod.RespList, len(cList))
for i, v := range cList {
var tmp = exchcodemod.RespList(v)
if v.InvalidAt.Before(time.Now()) {
tmp.Status = exchcodemod.StatusExpired
}
list[i] = tmp
}
return list
}
// 增加兑换码
func AddExchangeCode(param exchcodemod.AddReqParam, manager string) (code stderr.Code, err error) {
// 检查app类型
switch param.App {
case exchcodemod.AppTypeYSVideo:
case exchcodemod.AppTypePFVideo:
case exchcodemod.AppTypeLTVpn:
default:
code = stderr.ExchangeCodeInvalidApp
return code, err
}
// 检查权限
switch param.Authority {
case exchcodemod.AuthorityShortVideoVip:
case exchcodemod.AuthorityFilmVip:
case exchcodemod.AuthoritySuper:
case exchcodemod.AuthorityVideoCoupon:
case exchcodemod.Authority3dPermanentVIP:
param.Channels = []string{"XUA01"}
case exchcodemod.AuthorityGold:
// 获取管理员角色
admin, err := adminmod.FindOneByName(manager)
if err != nil || admin.ID.IsZero() {
code = stderr.AdminIDErr
return code, err
}
if admin.Role != SuperAdmin {
code = stderr.ExchangeCodeOperateDenied
return code, err
}
default:
code = stderr.ExchangeCodeInvalidAuthority
return code, err
}
// 检查渠道
if param.Channels == nil || len(param.Channels) == 0 {
param.Channels = []string{"system"}
}
// 获取批次号
eMod, err := exchcodemod.GetLatestBatchNumber(param.App)
if err != nil {
code = stderr.Failure
return
}
var batchNum = produceBatchNumber(param.App, eMod.BatchNum)
if batchNum == "" {
code = stderr.ExchangeCodeInvalidBatchNumber
return
}
var docs []exchcodemod.ExchCode
for i := 0; i < len(param.Channels); i++ {
for i2 := 0; int64(i2) < param.Count; i2++ {
var exchangeCode = common.InvitePromotionCodeGenera()
if exchangeCode == "" {
code = stderr.ExchangeCodeProduceCodeFailed
return
}
var now = time.Now()
var doc = exchcodemod.ExchCode{
BatchNum: batchNum,
App: param.App,
Channel: param.Channels[i],
Code: exchangeCode,
Authority: param.Authority,
Status: exchcodemod.StatusUnused,
Reward: param.Reward,
RewardCount: param.RewardCount,
UsableNum: param.UsableNum,
Operator: manager,
Remark: param.Remark,
EffectiveAt: param.EffectiveAt,
InvalidAt: param.InvalidAt,
CreatedAt: now,
UpdatedAt: now,
}
docs = append(docs, doc)
}
}
if err = exchcodemod.BulkWrite(docs); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
// 生成兑换码
func ProduceExchangeCode(n int) string {
if n >= 5 {
return ""
}
var length = 12
c := common.RandBase32Upper(length)
if mod, err := exchcodemod.GetExchangeCodeByCode(c); err != nil || mod != nil {
c = ProduceExchangeCode(n + 1)
}
return c
}
// 根据兑换类型和批次号删除兑换码你
func UpdateExchangeCode(param exchcodemod.UpdateReqParam, manager string) (code stderr.Code, err error) {
// 获取管理员角色
admin, err := adminmod.FindOneByName(manager)
if err != nil || admin.ID.IsZero() {
code = stderr.AdminIDErr
return
}
if param.Authority != nil && *param.Authority == exchcodemod.AuthorityGold && admin.Role != SuperAdmin {
code = stderr.ExchangeCodeOperateDenied
return
}
// 获取兑换码状态
cMode, err := exchcodemod.GetExchangeCodeByID(param.ID)
if err != nil || cMode == nil {
code = stderr.ExchangeCodeInvalidCode
return
}
if cMode.InvalidAt.Before(time.Now()) || cMode.Status == exchcodemod.StatusUsed {
code = stderr.ExchangeCodeForbidUpdate
return
}
var update = exchcodemod.UpdateDoc{
Status: param.Status,
Authority: param.Authority,
Channel: param.Channel,
Reward: param.Reward,
EffectiveAt: param.EffectiveAt,
InvalidAt: param.InvalidAt,
Remark: param.Remark,
UsableNum: param.UsableNum,
}
if err = exchcodemod.UpdateExchangeCode(param.ID, update); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
// 生成批次号
func produceBatchNumber(app exchcodemod.AppType, lastBatchNum string) string {
var suffix string
switch app {
case exchcodemod.AppTypeYSVideo:
suffix = "YS-"
case exchcodemod.AppTypePFVideo:
suffix = "PF-"
case exchcodemod.AppTypeLTVpn:
suffix = "LT-"
}
if lastBatchNum == "" {
return suffix + "0001"
}
s := strings.Split(lastBatchNum, "-")
if len(s) < 2 {
return ""
}
n := s[1]
num, _ := strconv.ParseInt(n, 10, 64)
num++
var batchNum string
if num >= 1000 {
batchNum = suffix + strconv.FormatInt(num, 10)
} else if num >= 100 && num < 1000 {
batchNum = suffix + fmt.Sprintf("0%d", num)
} else if num >= 10 && num < 100 {
batchNum = suffix + fmt.Sprintf("00%d", num)
} else {
batchNum = suffix + fmt.Sprintf("000%d", num)
}
return batchNum
}
+49
View File
@@ -0,0 +1,49 @@
package exchcodeser
import (
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/l/exchlogmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
// 获取兑换码日志列表
func GetExchangeTypeList(param exchlogmod.ListReqParam) (code stderr.Code, data interface{}, err error) {
var filterParam = exchlogmod.FilterDoc{
Code: param.Code,
UserID: param.UserID,
Channel: param.Channel,
Authority: param.Authority,
}
total, tList, err := exchlogmod.GetExchangeLogList(filterParam, param.Page)
if err != nil {
code = stderr.Failure
return
}
list := make([]exchlogmod.ListResp, len(tList))
for i, v := range tList {
// 兼容之前的数据
if v.RewardCount == 0 {
v.RewardCount = 1
}
list[i] = exchlogmod.ListResp{
Code: v.Code,
UserID: v.UserID,
Channel: v.Channel,
Authority: v.Authority,
Reward: v.Reward,
RewardCount: v.RewardCount,
CreatedAt: v.CreatedAt,
}
}
if tList == nil {
data = commod.ListResp{Total: 0, List: []exchlogmod.ExchangeLog{}}
} else {
data = commod.ListResp{Total: total, List: list}
}
code = stderr.Success
return
}
+431
View File
@@ -0,0 +1,431 @@
package exportser
import (
"container/list"
"errors"
"fmt"
"path/filepath"
"reflect"
"strconv"
"time"
"91porn-server/common/constant"
"91porn-server/common/file"
"91porn-server/common/log"
"91porn-server/models/s/statordermod"
"91porn-server/models/s/statvidmod"
"91porn-server/models/v/exchcodemod"
"91porn-server/models/v/rchgordmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/wdordmod"
"github.com/tealeg/xlsx"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Import 读取文件
func Import(inFile string) (data *list.List, errr error) {
// 打开文件
xlFile, err := xlsx.OpenFile(inFile)
if err != nil {
return nil, err
}
l := list.New()
// 遍历sheet页读取
for _, sheet := range xlFile.Sheets {
fmt.Println("sheet name: ", sheet.Name)
//遍历行读取
for _, row := range sheet.Rows {
var rowCell []string
// 遍历每行的列读取
for _, cell := range row.Cells {
text := cell.String()
rowCell = append(rowCell, text)
}
l.PushBack(rowCell)
}
}
return l, nil
}
type Student struct {
Name string
age int
Phone string
Gender string
Mail string
}
// Export 导出xlsx文件
func Export(typeFile int64) (string, error) {
switch typeFile {
case constant.ChannelLogTypeFile:
return ExampleExport()
default:
return "", errors.New("not this type of excel")
}
}
func ExampleExport() (string, error) {
var fileName = "导出excel案列文件.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("导出excel案列")
if err != nil {
fmt.Println(err.Error())
return "", err
}
data := getStudents()
//add data
for _, stu := range data {
row := sheet.AddRow()
nameCell := row.AddCell()
nameCell.Value = stu.Name
ageCell := row.AddCell()
ageCell.Value = strconv.Itoa(stu.age)
phoneCell := row.AddCell()
phoneCell.Value = stu.Phone
genderCell := row.AddCell()
genderCell.Value = stu.Gender
mailCell := row.AddCell()
mailCell.Value = stu.Mail
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
fmt.Println(fpath)
if err = f.Save(fpath); err != nil {
fmt.Println(err.Error())
return "", err
}
fmt.Println("\n\nexport success")
return fpath, nil
}
func StructToTitle(s *xlsx.Sheet, obj interface{}) {
t := reflect.TypeOf(obj)
row := s.AddRow()
for i := 0; i < t.NumField(); i++ {
k, _ := t.Field(i).Tag.Lookup("json")
c := row.AddCell()
c.Value = k
}
}
func StructToTitle2(s *xlsx.Sheet, obj interface{}, lableMap map[string]string) {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
row := s.AddRow()
for i := 0; i < t.NumField(); i++ {
vv := v.Field(i)
fv := vv.Type()
switch fv.Kind() {
case reflect.Struct:
}
k, _ := t.Field(i).Tag.Lookup("json")
lable, ok := lableMap[k]
if !ok {
lable = k
}
row.AddCell().Value = lable
}
}
func getValue(kind reflect.Kind, value reflect.Value) string {
switch kind {
case reflect.String:
return value.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(value.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(value.Uint(), 10)
case reflect.Bool:
return strconv.FormatBool(value.Bool())
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(value.Float(), 'f', 2, 64)
case reflect.Struct:
timeV, ok := value.Interface().(time.Time)
if ok {
return timeV.String()
}
if oid, ok := value.Interface().(primitive.ObjectID); ok {
return oid.Hex()
}
return ""
case reflect.Interface:
if timeV, ok := value.Interface().(time.Time); ok {
return timeV.String()
}
return ""
case reflect.Ptr:
if value.IsNil() {
return ""
}
value = value.Elem()
if value.Interface() != nil {
if timeV, ok := value.Interface().(time.Time); ok {
return timeV.String()
}
} else {
return ""
}
case reflect.Array:
if id, ok := value.Interface().(primitive.ObjectID); ok {
if id.IsZero() {
return ""
} else {
return id.Hex()
}
}
}
return "未知"
}
func StructToRow(row *xlsx.Row, obj interface{}) error {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
for i := 0; i < t.NumField(); i++ {
fv := v.Field(i).Type()
vv := v.Field(i)
c := row.AddCell()
c.Value = getValue(fv.Kind(), vv)
}
return nil
}
func UserListExport(path string, index int, users []usermod.ExportUser) (string, error) {
var fileName = fmt.Sprintf("用户列表_%v.xlsx", index)
f := xlsx.NewFile()
sheet, err := f.AddSheet("用户列表")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, usermod.ExportUser{})
//add data
for _, u := range users {
row := sheet.AddRow()
_ = StructToRow(row, u)
}
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
fmt.Println(fpath)
err = f.Save(fpath)
if err != nil {
fmt.Println(err.Error())
return "", err
}
fmt.Println("\n\nexport success")
return fpath, nil
}
func getStudents() []Student {
students := make([]Student, 10)
for i := 0; i < 10; i++ {
stu := Student{}
stu.Name = "name" + strconv.Itoa(i+1)
stu.Mail = stu.Name + "@chairis.cn"
stu.Phone = "1380013800" + strconv.Itoa(i)
stu.age = 20
stu.Gender = "男"
students[i] = stu
}
return students
}
// RechargeOrderExport 充值订单存xlsx
func RechargeOrderExport(orders []*rchgordmod.RchargeOrderExport) (string, error) {
var fileName = "充值订单.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("充值订单")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, rchgordmod.RchargeOrderExport{})
//add data
for _, o := range orders {
row := sheet.AddRow()
_ = StructToRow(row, *o)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
// WithdrawOrderExport 提现订单存xlsx
func WithdrawOrderExport(orders []*wdordmod.WithdrawOrder) (string, error) {
var fileName = "提现订单.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("提现订单")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, wdordmod.WithdrawOrder{})
//add data
for _, o := range orders {
row := sheet.AddRow()
_ = StructToRow(row, *o)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
// GoldTurnoverExport 金币流水存xlsx
func GoldTurnoverExport(turnover []*txnmod.TransactionLog) (string, error) {
var fileName = "金币流水.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("金币流水")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, txnmod.TransactionLog{})
//add data
for _, t := range turnover {
row := sheet.AddRow()
_ = StructToRow(row, *t)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
func changeVideoStatRecord(incomes []statvidmod.VideoStatRecord) []statvidmod.VideoStatExport {
infos := make([]statvidmod.VideoStatExport, len(incomes))
for i, v := range incomes {
vs := statvidmod.VideoStatExport{}
if v.SumDate != nil {
vs.SumDate = *v.SumDate
}
if v.Video.ID != nil {
vs.ID = *v.Video.ID
}
if v.Video.Title != nil {
vs.Title = *v.Video.Title
}
if v.Video.PublisherID != nil {
vs.PublisherID = *v.Video.PublisherID
}
if v.Video.Coins != nil {
vs.Coins = *v.Video.Coins
}
if v.Income != nil {
vs.Income = *v.Income
}
if v.Tax != nil {
vs.Tax = *v.Tax
}
if v.PayCount != nil {
vs.PayCount = *v.PayCount
}
if v.CreatedAt != nil {
vs.CreatedAt = *v.CreatedAt
}
infos[i] = vs
}
return infos
}
// VideoIncomeExport 视频收入存xlsx
func VideoIncomeExport(incomes []statvidmod.VideoStatRecord) (string, error) {
infos := changeVideoStatRecord(incomes)
var fileName = "视频收入.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("视频收入")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, statvidmod.VideoStatExport{})
//add data
for _, i := range infos {
row := sheet.AddRow()
_ = StructToRow(row, i)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
// OrderStatExport 订单统计存xlsx
func OrderStatExport(orders []statordermod.OrderState) (string, error) {
var fileName = "订单统计.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("订单统计")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, statordermod.OrderState{})
//add data
for _, o := range orders {
row := sheet.AddRow()
_ = StructToRow(row, o)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
// ExchangeCodeExport 兑换码导出
func ExchangeCodeExport(codes []exchcodemod.RespList) (string, error) {
var fileName = "兑换码.xlsx"
f := xlsx.NewFile()
sheet, err := f.AddSheet("兑换码")
if err != nil {
fmt.Println(err.Error())
return "", err
}
StructToTitle(sheet, exchcodemod.RespList{})
//add data
for _, c := range codes {
row := sheet.AddRow()
_ = StructToRow(row, c)
}
var path, _ = filepath.Abs("temp")
if err = file.MakeDir(path); err != nil {
log.Warn("can't make fs dir", log.E(err))
}
fpath := path + "/" + fileName
if err = f.Save(fpath); err != nil {
return "", err
}
return fpath, nil
}
+55
View File
@@ -0,0 +1,55 @@
package ffser
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"os/exec"
"strconv"
"strings"
"91porn-server/common/crypt"
)
// AvFormat 调用ffmpeg 获取信息
func AvFormat(fpath string) (resloution string, playTime uint, err error) {
args := makeArgs(fpath, []string{"width", "height", "duration"}...)
fmt.Printf("%v", args)
command := exec.CommandContext(context.Background(), "ffprobe", args...)
out, err := command.CombinedOutput()
if err != nil {
fmt.Printf("ffprobe convert to ts wrong %+v", err)
return
}
if strings.Contains(string(out), "Invalid argument") {
err = errors.New("ffmpeg convert err")
return
}
result, _ := crypt.JSON2Map(string(out))
if v, ok := result["streams"].([]interface{}); ok {
if v2, ok := v[0].(map[string]interface{}); ok {
width, _ := json.Marshal(v2["width"])
height, _ := json.Marshal(v2["height"])
duration, _ := v2["duration"].(string)
resloution = string(width) + "*" + string(height)
pt, _ := strconv.ParseFloat(string(duration), 32)
playTime = uint(math.Ceil(pt))
fmt.Println(playTime)
return
}
}
return
}
func makeArgs(path string, command ...string) []string {
com := strings.Join(command, ",")
return []string{
"-v", "quiet",
"-v", "quiet",
"-select_streams", "v", "-show_entries",
"stream=" + com,
"-i", path,
}
}
+67
View File
@@ -0,0 +1,67 @@
package filterser
import (
"time"
"91porn-server/common/filter"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/filtermod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// 获取敏感词列表
func GetFilterWordList(content *string, page commod.Page) (code stderr.Code, data interface{}, err error) {
total, wordList, err := filtermod.GetFilterWordList(content, page)
if err != nil {
code = stderr.Failure
return
}
list := make([]filtermod.WordListResp, len(wordList))
for i, v := range wordList {
list[i] = filtermod.WordListResp(v)
}
data = commod.ListResp{Total: total, List: list}
code = stderr.Success
return
}
// 添加过滤词
func AddFilterWord(words []string) (code stderr.Code, err error) {
docs := make([]filtermod.WordsFilter, len(words))
for i, v := range words {
docs[i] = filtermod.WordsFilter{
Word: v,
CreatedAt: time.Now(),
}
}
if len(docs) == 0 {
code = stderr.Success
return
}
if err = filtermod.InsertMany(docs); err != nil {
code = stderr.Failure
return
}
if err = filter.UpdateFilterWords(); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
// 删除过滤词
func DeleteFilterWords(ids []primitive.ObjectID) (code stderr.Code, err error) {
if err = filtermod.DeleteFilterWords(ids); err != nil {
code = stderr.Failure
return
}
if err = filter.UpdateFilterWords(); err != nil {
code = stderr.Failure
return
}
code = stderr.Success
return
}
+129
View File
@@ -0,0 +1,129 @@
package imagetopser
import (
"fmt"
"time"
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/imagetopmod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func FindImageTopList(req imagetopmod.ImageTopListReq) (code stderr.Code, data interface{}) {
resp := imagetopmod.ImageTopListRes{
Total: 0,
List: make([]imagetopmod.ImageTopResp, 0),
}
//查询帖子列表
skip := (req.PageNumber - 1) * req.PageSize
var filter = bson.M{}
switch req.TopType {
case 1: //推荐
filter = bson.M{"recommendSort": bson.M{"$gt": 0}}
case 2: //最新
filter = bson.M{"newSort": bson.M{"$gt": 0}}
}
sort := bson.D{{Key: "updatedAt", Value: -1}}
opts := options.Find().SetSort(sort).SetSkip(int64(skip)).SetLimit(int64(req.PageSize))
imageTopList, err := imagetopmod.GetImageTopList(filter, opts)
if err != nil {
log.Error(fmt.Sprintf("获取视频列表 发生错误:%v", err), log.Any("params", req))
return stderr.ErrDbQueryError, nil
}
//获取置顶视频数量
total, _ := imagetopmod.GetImageTopCount(filter)
resp.Total = total
//根据视频ID获取置顶视频信息
resp.List = make([]imagetopmod.ImageTopResp, len(imageTopList))
for i, v := range imageTopList {
vidInfo, _ := vidmod.GetVideoByID(v.ImageId)
resp.List[i] = imagetopmod.ImageTopResp{
ID: vidInfo.ID,
NewsType: vidInfo.NewsType,
PubliserID: vidInfo.PublisherID,
Title: vidInfo.Title,
SourceID: vidInfo.SourceID,
SourceURL: vidInfo.SourceURL,
FileName: vidInfo.Filename,
PlayTime: vidInfo.PlayTime,
Status: vidInfo.Status,
Cover: vidInfo.Cover,
CoverThumb: vidInfo.CoverThumb,
SeriesCover: vidInfo.SeriesCover,
PlayCount: vidInfo.PlayCount,
LikeCount: vidInfo.LikeCount,
CommentCount: vidInfo.CommentCount,
ShareCount: vidInfo.ShareCount,
FakeLikeCount: vidInfo.FakeLikeCount,
FakeCommentCount: vidInfo.FakeCommentCount,
FakeShareCount: vidInfo.FakeShareCount,
FakePlayCount: vidInfo.FakePlayCount,
Coins: vidInfo.Coins,
Resolution: vidInfo.Resolution,
FreeTime: vidInfo.FreeTime,
Chosen: vidInfo.Chosen,
FreeArea: vidInfo.FreeArea,
Size: vidInfo.Size,
CreatedAt: vidInfo.CreatedAt,
UpdatedAt: vidInfo.UpdatedAt,
Reason: vidInfo.Reason,
IsTopping: vidInfo.IsTopping,
IsRecommend: vidInfo.IsRecommend,
IsChoosen: vidInfo.IsChoosen,
RecoWeight: vidInfo.RecoWeight,
ReviewAccount: vidInfo.ReviewAccount,
LinkUrl: vidInfo.LinkUrl,
SortCode: vidInfo.SortCode,
Rewarded: vidInfo.Rewarded,
FakeRewarded: vidInfo.FakeRewarded,
LiaoBaTop: vidInfo.LiaoBaTop,
LiaoBaTopSort: vidInfo.LiaoBaTopSort,
WorksSort: vidInfo.WorksSort,
PageViewCount: vidInfo.PageViewCount,
ActivityID: vidInfo.ActivityID,
NewSort: v.NewSort,
NewEndTime: *v.NewEndTime,
RecommendSort: v.RecommendSort,
RecommendEndTime: *v.RecommendEndTime,
}
}
code = stderr.Success
data = resp
return
}
func UpdateImageTop(req imagetopmod.EditReq) (code stderr.Code, data interface{}) {
doc := imagetopmod.ImageTopModel{
ImageId: req.ID,
NewSort: req.NewSort,
RecommendSort: req.RecommendSort,
NewEndTime: &req.NewEndTime,
RecommendEndTime: &req.RecommendEndTime,
UpdatedAt: time.Now(),
}
update, _ := common.ToBsonM(doc)
if _, err := imagetopmod.UpdateImageTop(req.ID, update); err != nil {
log.Error(fmt.Sprintf("更新置顶帖子 发生错误:%v", err), log.Any("params", req))
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, nil
}
func DeleteImageTop(req imagetopmod.DeleteReq) (code stderr.Code, data interface{}) {
if err := imagetopmod.DeleteOne(req.Id); err != nil {
log.Error(fmt.Sprintf("删除置顶贴子 发生错误:%v", err), log.Any("params", req))
return stderr.ErrDbDeleteError, nil
}
set := bson.M{"happinessPlazaTop": 0}
//兼容老版本,将幸福广场参数置为0
if _, err := vidmod.UpdateOneByID(req.Id, set); err != nil {
log.Error(fmt.Sprintf("更新视频信息 发生错误:%v", err), log.Any("params", req))
return stderr.ErrDbUpdateError, nil
}
return stderr.Success, nil
}
+73
View File
@@ -0,0 +1,73 @@
package imgroupmemberser
import (
"time"
"91porn-server/models/cache/imgroupmemberdata"
"91porn-server/models/commod"
"91porn-server/models/v/imgroupmembermod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []imgroupmembermod.ImGroupMember `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
res.List, res.Total, res.HasNext, err = imgroupmembermod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebCreateReq struct {
GroupId int64 `json:"groupId" bson:"groupId"` // 群组id
Uid uint64 `json:"uid" form:"uid" binding:"required"` // uid
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := imgroupmembermod.ImGroupMember{
GroupId: p.GroupId,
Uid: p.Uid,
Status: 1,
CreatedAt: time.Now(),
}
// 创建数据
if _, err := imgroupmemberdata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Status *int `json:"status" bson:"status"` // 0-禁言 1-可用 2-已移除
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := imgroupmembermod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Status != nil {
data["status"] = *p.Status
}
if _, err = imgroupmemberdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
+106
View File
@@ -0,0 +1,106 @@
package imgroupser
import (
"math/rand"
"time"
"91porn-server/models/cache/imgroupdata"
"91porn-server/models/commod"
"91porn-server/models/v/imgroupmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []imgroupmod.ImGroup `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
res.List, res.Total, res.HasNext, err = imgroupmod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebCreateReq struct {
Name string `json:"name" form:"name" binding:"required"` // 群组名
Cover string `json:"cover" form:"cover" binding:"required"` // 封面
Summary string `json:"summary" form:"summary" binding:"required"` // 简介
FakeMemberNum int64 `json:"fakeMemberNum" form:"fakeMemberNum"` // 成员数量(假的)
Price int64 `json:"price" form:"price"` // 加入群聊价格 0-免费
Status int `json:"status" form:"status"` // 0-不可用 1-可用
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := imgroupmod.ImGroup{
GroupId: 10000000 + rand.Int63n(89999999), // 随机一个
Name: p.Name,
Cover: p.Cover,
Summary: p.Summary,
FakeMemberNum: p.FakeMemberNum,
Price: p.Price,
Status: p.Status,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 创建数据
if _, err := imgroupdata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Name *string `json:"name"` // 群组名
Cover *string `json:"cover"` // 封面
Summary *string `json:"summary"` // 简介
FakeMemberNum *int64 `json:"fakeMemberNum"` // 成员数量(假的)
Price *int64 `json:"price"` // 加入群聊价格 0-免费
Status *int `json:"status" form:"status"` // 0-不可用 1-可用
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := imgroupmod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Name != nil {
data["name"] = *p.Name
}
if p.Cover != nil {
data["cover"] = *p.Cover
}
if p.Summary != nil {
data["summary"] = *p.Summary
}
if p.FakeMemberNum != nil {
data["fakeMemberNum"] = *p.FakeMemberNum
}
if p.Price != nil {
data["price"] = *p.Price
}
if p.Status != nil {
data["status"] = *p.Status
}
data["updatedAt"] = time.Now()
if _, err = imgroupdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
+106
View File
@@ -0,0 +1,106 @@
package immessageser
import (
"time"
"91porn-server/models/cache/immessagedata"
"91porn-server/models/commod"
"91porn-server/models/v/immessagemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
GroupId *int64 `json:"groupId" form:"groupId"`
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []immessagemod.ImMessage `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
if q.GroupId != nil {
filter["groupId"] = *q.GroupId
}
res.List, res.Total, res.HasNext, err = immessagemod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebCreateReq struct {
GroupId int64 `json:"groupId" form:"groupId" binding:"required"` // 群组id
Uid uint64 `json:"uid" form:"uid" binding:"required"` // 用户id
Content string `json:"content" form:"content"` // 消息内容
Image string `json:"image" form:"image"` // 图片
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := immessagemod.ImMessage{
GroupId: p.GroupId,
Uid: p.Uid,
Content: p.Content,
Image: p.Image,
CreatedAt: time.Now(),
}
// 创建数据
if _, err := immessagedata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
GroupId *int64 `json:"groupId"` // 群组id
Uid *int64 `json:"uid"` // 用户id
Content *string `json:"content"` // 消息内容
Image *string `json:"image"` // 图片
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := immessagemod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.GroupId != nil {
data["groupId"] = *p.GroupId
}
if p.Uid != nil {
data["uid"] = *p.Uid
}
if p.Content != nil {
data["content"] = *p.Content
}
if p.Image != nil {
data["image"] = *p.Image
}
if _, err = immessagedata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := immessagedata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
+197
View File
@@ -0,0 +1,197 @@
package infmtser
import (
"time"
"91porn-server/common/pageopt"
"91porn-server/models/v/noticefmtmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ObjectID = primitive.ObjectID
type Sender = noticefmtmod.Sender
type Receiver = noticefmtmod.Receiver
type UIDSlice = noticefmtmod.UIDSlice
type NoticeType = noticefmtmod.NoticeType
type RegularNotice = noticefmtmod.RegularNotice
type PlanNotice = noticefmtmod.PlanNotice
type SpecifyNotice = noticefmtmod.SpecifyNotice
type AfterRegistNotice = noticefmtmod.AfterRegistNotice
type MailNotice = noticefmtmod.MailNotice
type TimeUnit = noticefmtmod.TimeUnit
type TimeSlice = noticefmtmod.TimeSlice
type Record struct {
ID ObjectID `json:"_id,omitempty"` //_id
NoticeCode string `json:"noticeCode"` //通知码 唯一标记不同的通知计划,可代替_id,方便复制数据
Title string `json:"title"` //标题
Sender Sender `json:"sender"` //发送者
Receiver Receiver `json:"receiver"` //接受者
Content string `json:"content"` //内容
Remark string `json:"remark"` //备注
NoticeType NoticeType `json:"noticeType"` //通知类型
Enable bool `json:"enable"` //消息开关 true:允许通知Receiver,否则不允许通知Receiver
UIDList UIDSlice `json:"uidList"` //用户列表
Rule string `json:"rule"` //描述
StartAt *time.Time `json:"startAt"` //缺省字段
EndAt *time.Time `json:"endAt"` //缺省字段
TimeUnit *TimeUnit `json:"timeUnit"` //缺省字段
TimeCount *int64 `json:"timeCount"` //缺省字段
PlanCount *int64 `json:"planCount"` //缺省字段
SpecifyTimeList TimeSlice `json:"specifyTimeList"` //缺省字段
}
type NoticePage struct {
Total int64 `json:"total" bson:"total"`
List []Record `json:"list" bson:"list"`
}
type TitleMatch = noticefmtmod.TitleMatch
type NoticeCodeMatch = noticefmtmod.NoticeCodeMatch
type NoticeTypeMatch = noticefmtmod.NoticeTypeMatch
type SenderMatch = noticefmtmod.SenderMatch
type ReceiverMatch = noticefmtmod.ReceiverMatch
type EnableMatch = noticefmtmod.EnableMatch
func NoticePages(skip, limit int64,
titleMatch TitleMatch,
noticeCodeMatch NoticeCodeMatch,
noticeTypeMatch NoticeTypeMatch,
senderMatch SenderMatch,
receiverMatch ReceiverMatch,
enableMatch EnableMatch,
) (NoticePage, error) {
mats := []pageopt.Matcher{
titleMatch.New(),
noticeCodeMatch.New(),
noticeTypeMatch.New(),
senderMatch.New(),
receiverMatch.New(),
enableMatch.New(),
}
sort := bson.D{{Key: "createdAt", Value: -1}}
list, err := noticefmtmod.List(sort, skip, limit, mats...)
if err != nil {
return NoticePage{}, err
}
recordList := make([]Record, len(list))
for i, v := range list {
record := Record{
ID: v.ID,
NoticeCode: v.NoticeCode,
Title: v.Title,
Sender: v.Sender,
Receiver: v.Receiver,
Content: v.Content,
Remark: v.Remark,
NoticeType: v.NoticeType,
Enable: v.Enable,
UIDList: v.UIDList,
}
switch v.NoticeType {
case noticefmtmod.RegularNoticeType:
record.Rule = v.RegularNotice.Rule()
record.StartAt = v.RegularNotice.StartAt
record.TimeUnit = v.RegularNotice.TimeUnit
record.TimeCount = v.RegularNotice.TimeCount
case noticefmtmod.PlanNoticeType:
record.Rule = v.PlanNotice.Rule()
record.StartAt = v.PlanNotice.StartAt
record.EndAt = v.PlanNotice.EndAt
record.PlanCount = v.PlanNotice.PlanCount
case noticefmtmod.SpecifyNoticeType:
record.Rule = v.SpecifyNotice.Rule()
record.SpecifyTimeList = v.SpecifyNotice.SpecifyTimeList
case noticefmtmod.AfterRegistNoticeType:
record.Rule = v.AfterRegistNotice.Rule()
record.TimeUnit = v.AfterRegistNotice.TimeUnit
record.TimeCount = v.AfterRegistNotice.TimeCount
}
recordList[i] = record
}
count, err := noticefmtmod.Count(mats...)
if err != nil {
return NoticePage{}, err
}
return NoticePage{
count,
recordList,
}, nil
}
type Mail struct {
ID ObjectID `json:"_id,omitempty"` //_id
NoticeCode string `json:"noticeCode"` //通知码 唯一标记不同的通知计划,可代替_id,方便复制数据
Title string `json:"title"` //标题
Receiver Receiver `json:"receiver"` //接受者
Content string `json:"content"` //内容
Remark string `json:"remark"` //备注
Enable bool `json:"-"` //消息开关 true:允许通知Receiver,否则不允许通知Receiver
UIDList UIDSlice `json:"uidList"` //用户列表
SendAt time.Time `json:"sendAt"` //发送时间
}
type MailPage struct {
Total int64 `json:"total" bson:"total"`
List []Mail `json:"list" bson:"list"`
}
func MailPages(skip, limit int64,
titleMatch TitleMatch,
noticeCodeMatch NoticeCodeMatch,
receiverMatch ReceiverMatch,
) (MailPage, error) {
noticeType := noticefmtmod.MailNoticeType
mats := []pageopt.Matcher{
(&NoticeTypeMatch{NoticeType: &noticeType}).New(),
titleMatch.New(),
noticeCodeMatch.New(),
receiverMatch.New(),
}
sort := bson.D{{Key: "createdAt", Value: -1}}
list, err := noticefmtmod.List(sort, skip, limit, mats...)
if err != nil {
return MailPage{}, err
}
recordList := make([]Mail, len(list))
for i, v := range list {
record := Mail{
ID: v.ID,
NoticeCode: v.NoticeCode,
Title: v.Title,
Receiver: v.Receiver,
Content: v.Content,
Remark: v.Remark,
Enable: v.Enable,
UIDList: v.UIDList,
}
if v.NoticeType == noticefmtmod.MailNoticeType {
if v.MailNotice.SendAt != nil {
record.SendAt = *v.MailNotice.SendAt
}
}
recordList[i] = record
}
count, err := noticefmtmod.Count(mats...)
if err != nil {
return MailPage{}, err
}
return MailPage{
count,
recordList,
}, nil
}
@@ -0,0 +1,52 @@
package integarl_exchange_ser
import (
"91porn-server/common/log"
"91porn-server/models/v/integralexcangemod"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func Edit(in *integralexcangemod.EditCond, manager string) error {
oid, _ := primitive.ObjectIDFromHex(in.ID)
vp, err := integralexcangemod.FindOneById(oid)
if err != nil {
return err
}
if vp.ID.IsZero() {
return errors.New("integral config is null")
}
if *in.Status == integralexcangemod.REFUSE {
// 退还积分
}
if err = integralexcangemod.Edit(in.Filter(), in.Update(manager)); err != nil {
log.Error(fmt.Sprintf("integralconfigmod update error:%+v:", err))
return err
}
return nil
}
func QueryAll(in *integralexcangemod.WebListRequest) (*integralexcangemod.QueryAllRes, error) {
var out = &integralexcangemod.QueryAllRes{}
count, err := integralexcangemod.QueryAllCount(in.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*integralexcangemod.IntegralExchange{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = integralexcangemod.QueryAllList(in.Filter(), in.Options())
if err != nil {
log.Error(fmt.Sprintf("integralexcangemod queryAllList error:%+v:", err))
return nil, err
}
return out, nil
}
@@ -0,0 +1,54 @@
package integralconfigser
import (
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/integralconfigmod"
"errors"
"fmt"
)
func Add(in *integralconfigmod.AddCond, manager string) stderr.Code {
err := integralconfigmod.InsertOne(nil, in.Generate(manager))
if err != nil {
return stderr.ErrDbInsertError
}
return stderr.Success
}
func Edit(in *integralconfigmod.EditCond, manager string) error {
vp, err := integralconfigmod.FindOneById(in.ID)
if err != nil {
return err
}
if vp.ID.IsZero() {
return errors.New("integral config is null")
}
if err = integralconfigmod.Edit(in.Filter(), in.Update(manager)); err != nil {
log.Error(fmt.Sprintf("integralconfigmod update error:%+v:", err))
return err
}
return nil
}
func QueryAll(in *integralconfigmod.QueryAllCond) (*integralconfigmod.QueryAllRes, error) {
var out = &integralconfigmod.QueryAllRes{}
count, err := integralconfigmod.QueryAllCount(in.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*integralconfigmod.IntegralConfig{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = integralconfigmod.QueryAllList(in.Filter(), in.Options())
if err != nil {
log.Error(fmt.Sprintf("integralconfigmod queryAllList error:%+v:", err))
return nil, err
}
return out, nil
}
+76
View File
@@ -0,0 +1,76 @@
package jingangser
import (
"91porn-server/models/v/jingangmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
func UpdateJingang(r UpdateJingangReq) error {
cond := make(bson.M)
if r.Name != nil {
cond["name"] = *r.Name
}
if r.Img != nil {
cond["img"] = *r.Img
}
if r.Desc != nil {
cond["desc"] = *r.Desc
}
if r.LinkType != nil {
cond["link_type"] = *r.LinkType
}
if r.LinkUrl != nil {
cond["link_url"] = *r.LinkUrl
}
if r.Sort != nil {
cond["sort"] = *r.Sort
}
if r.Status != nil {
cond["status"] = *r.Status
}
if !r.Mid.IsZero() {
cond["mid"] = r.Mid
}
if len(cond) == 0 {
return nil
}
cond["updatedAt"] = time.Now()
return jingangmod.EditJingang(nil, r.ID, cond)
}
func AddJingang(r AddJingangReq) (*primitive.ObjectID, error) {
now := time.Now()
j := jingangmod.JingangModel{
Name: r.Name,
CreatedAt: now,
UpdatedAt: now,
}
if r.Img != nil {
j.Img = *r.Img
}
if r.Desc != nil {
j.Desc = *r.Desc
}
if r.LinkType != nil {
j.LinkType = *r.LinkType
}
if r.LinkUrl != nil {
j.LinkUrl = *r.LinkUrl
}
if r.Sort != nil {
j.Sort = *r.Sort
}
if r.Status != nil {
j.Status = *r.Status
}
if !r.Mid.IsZero() {
j.Mid = r.Mid
}
return jingangmod.AddJingang(nil, j)
}
func DeleteJingang(r DeleteJingangReq) error {
return jingangmod.DeleteJingang(nil, r.ID)
}
+35
View File
@@ -0,0 +1,35 @@
package jingangser
import "go.mongodb.org/mongo-driver/bson/primitive"
type JingangListRequest struct {
PageNum uint64 `form:"pageNum" json:"pageNum" binding:"required,min=1"` // 页码
PageSize uint64 `form:"pageSize" json:"pageSize" binding:"required,min=1"` // 每页展示数
}
type UpdateJingangReq struct {
ID primitive.ObjectID `json:"id" form:"id" binding:"required"` // 金刚id
Mid primitive.ObjectID `json:"mid" bson:"mid"` // 所属模块
Name *string `json:"name" form:"name"` // 名称
Img *string `json:"img" form:"img"` // 图片地址
Desc *string `json:"desc" form:"desc"` // 说明
LinkType *int64 `json:"link_type" form:"link_type"` // 1 内部链接; 2 外部连接
LinkUrl *string `json:"link_url" form:"link_url"` // 链接地址
Sort *uint64 `json:"sort" form:"sort"` // 排序
Status *uint64 `json:"status" form:"status"` // 1 正常; 2 禁用
}
type AddJingangReq struct {
Mid primitive.ObjectID `json:"mid" bson:"mid"` // 所属模块
Name string `json:"name" form:"name" binding:"required"` // 名称
Img *string `json:"img" form:"img"` // 图片地址
Desc *string `json:"desc" form:"desc"` // 说明
LinkType *int64 `json:"link_type" form:"link_type"` // 1 内部链接; 2 外部链接
LinkUrl *string `json:"link_url" form:"link_url"` // 链接地址
Sort *uint64 `json:"sort" form:"sort"` // 排序
Status *uint64 `json:"status" form:"status"` // 1 正常; 2 禁用
}
type DeleteJingangReq struct {
ID primitive.ObjectID `json:"id" form:"id" binding:"required"`
}
+266
View File
@@ -0,0 +1,266 @@
package laosijiser
import (
"91porn-server/common"
"91porn-server/common/laosiji"
"91porn-server/common/log"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/web/service/vidser"
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func ComicsSearch(ctx context.Context, param laosiji.ComicsSearchListReq) (resp laosiji.ComicsSearchListResp, err error) {
comicsResp, err := laosiji.ComicsSearch(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
//是否导入
lsjIds := make([]string, 0)
for _, v := range comicsResp.Data {
lsjIds = append(lsjIds, v.Id)
}
comicsList, _ := mediamod.GetListByLsjIds(lsjIds)
// 处理域名
resp = laosiji.ComicsSearchListResp{
Data: make([]laosiji.ComicsSearchInfo, 0),
Total: comicsResp.Total,
Current_page: comicsResp.Current_page,
Page_size: comicsResp.Page_size,
Last_page: comicsResp.Last_page,
}
for _, comicsInfo := range comicsResp.Data {
//comicsInfo.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, comicsInfo.Img)
comicsInfo.Img = common.JoinUrlPath("laosiji", comicsInfo.Img)
for _, comicsIn := range comicsList {
if comicsIn.LsjId == comicsInfo.Id {
comicsInfo.IsAdd = true
continue
}
}
resp.Data = append(resp.Data, comicsInfo)
}
return resp, nil
}
func ComicsDetail(ctx context.Context, param laosiji.ComicsDetailReq) (resp laosiji.ComicsDetailResp, serr error) {
resp, serr = laosiji.ComicsDetail(ctx, param)
if serr != nil {
log.Error("MovieSearch", log.E(serr))
return
}
// 处理域名
//resp.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Img)
resp.Img = common.JoinUrlPath("laosiji", resp.Img)
if resp.Chapter != nil && len(resp.Chapter) > 0 {
for i, cha := range resp.Chapter {
if len(cha.Img) > 0 {
//resp.Chapter[i].Img = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Chapter[i].Img)
resp.Chapter[i].Img = common.JoinUrlPath("laosiji", resp.Chapter[i].Img)
}
if cha.Content != nil && len(cha.Content) > 0 {
for y, content := range cha.Content {
//resp.Chapter[i].Content[y].F = common.JoinUrlPath(laosiji.IMAGEYUAN, content.F)
resp.Chapter[i].Content[y].F = common.JoinUrlPath("laosiji", content.F)
}
}
}
}
return
}
func ComicsAddList(ctx context.Context, param laosiji.ComicsSyncReq, manager string) (hits []vidser.SensitiveHit, err error) {
if param.Ids == nil || len(param.Ids) <= 0 {
return
}
// 批量查询所有详情
comicsDetails := make([]laosiji.ComicsDetailResp, 0)
for _, id := range param.Ids {
var comicsDetail laosiji.ComicsDetailResp
req := laosiji.ComicsDetailReq{
Id: id,
}
comicsDetail, err = laosiji.ComicsDetail(ctx, req)
if err != nil {
log.Warn("ComicsAddList laosiji.ComicsDetail failed", log.Any("id", id), log.E(err))
continue
}
// 处理域名
//comicsDetail.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, comicsDetail.Img)
comicsDetail.Img = common.JoinUrlPath("laosiji", comicsDetail.Img)
if comicsDetail.Chapter != nil && len(comicsDetail.Chapter) > 0 {
for i, cha := range comicsDetail.Chapter {
if len(cha.Img) > 0 {
comicsDetail.Chapter[i].Img = common.JoinUrlPath("laosiji", comicsDetail.Chapter[i].Img)
}
if cha.Content != nil && len(cha.Content) > 0 {
for y, content := range cha.Content {
comicsDetail.Chapter[i].Content[y].F = common.JoinUrlPath("laosiji", content.F)
}
}
}
}
comicsDetails = append(comicsDetails, comicsDetail)
}
if len(comicsDetails) <= 0 {
log.Warn("ComicsAddList laosiji.ComicsDetailResp len is 0")
err = errors.New("laosiji.ComicsDetailResp len is 0")
return
}
// 批量处理标签
tags := make(map[string]struct{})
for _, comicsDetail := range comicsDetails {
if comicsDetail.Tags != nil && len(comicsDetail.Tags) > 0 {
for _, tag := range comicsDetail.Tags {
tags[tag.Name] = struct{}{}
}
}
}
tagMaps := make(map[string]primitive.ObjectID)
if len(tags) > 0 {
tagList := make([]string, 0)
for k, _ := range tags {
tagList = append(tagList, k)
}
tagMaps, err = checkInsertMediaTag(tagList, 2)
if err != nil {
log.Error("ComicsAdd checkInsertMediaTag failed", log.E(err))
return
}
}
terms := vidser.LoadEnabledSensitiveTerms()
hits = make([]vidser.SensitiveHit, 0)
failedIds := make([]string, 0)
for _, comicsDetail := range comicsDetails {
hit, oneErr := syncOneComics(comicsDetail, tagMaps, manager, terms)
if oneErr != nil {
log.Warn("ComicsAdd syncOneComics failed", log.Any("id", comicsDetail.Id), log.E(oneErr))
failedIds = append(failedIds, comicsDetail.Id)
continue
}
if hit != nil {
hits = append(hits, *hit)
}
}
if len(failedIds) > 0 {
err = errors.New(fmt.Sprintf("需要同步%d部漫画,失败%d,失败Id[%s]", len(param.Ids), len(failedIds), strings.Join(failedIds, ",")))
}
return
}
func syncOneComics(comicsDetail laosiji.ComicsDetailResp, tagMaps map[string]primitive.ObjectID, manager string, terms []string) (*vidser.SensitiveHit, error) {
now := time.Now()
mediaTemp, err := mediamod.GetListByLsjId(comicsDetail.Id)
if err != nil {
log.Warn("syncOneComics GetListByLsjId failed", log.Any("id", comicsDetail.Id), log.Any("err", err))
return nil, err
}
if !mediaTemp.ID.IsZero() {
return nil, nil
}
mediaTemp = mediamod.Media{
ID: primitive.NewObjectID(),
LsjId: comicsDetail.Id,
Title: comicsDetail.Name,
HorizontalCover: comicsDetail.Img,
VerticalCover: comicsDetail.Img,
Tags: make([]primitive.ObjectID, 0),
TagDetails: make([]mediamod.TagDetail, 0),
Summary: comicsDetail.Description,
FreeEpisode: 0,
CurrentEpisode: 0,
MediaType: mediamod.MediaTypeImage,
Kind: 5,
PermissionIconHide: false,
Permission: 2,
Price: 0,
ContentsPrice: 0,
Status: 1,
CreatedAt: now,
UpdateTime: now,
UpdatedAct: manager,
}
mediaTemp.TotalEpisode, _ = strconv.Atoi(comicsDetail.Chapter_count)
mediaTemp.UpdateStatus, _ = strconv.Atoi(comicsDetail.Update_status)
mediaTemp.Price, _ = strconv.ParseInt(comicsDetail.Money, 10, 64)
if mediaTemp.Price > 0 {
mediaTemp.Permission = 1
}
//if mediaTemp.UpdateStatus == 1 {
// mediaTemp.UpdatedDesc = fmt.Sprintf("已完结%d话", mediaTemp.TotalEpisode)
//} else {
// mediaTemp.UpdatedDesc = fmt.Sprintf("更新至%d话", mediaTemp.TotalEpisode)
//}
for _, tag := range comicsDetail.Tags {
if v, ok := tagMaps[tag.Name]; ok {
mediaTemp.TagDetails = append(mediaTemp.TagDetails, mediamod.TagDetail{
Id: v,
Name: tag.Name,
})
mediaTemp.Tags = append(mediaTemp.Tags, v)
}
}
contents := make([]mediacontentmod.MediaContent, 0)
if len(comicsDetail.Chapter) > 0 {
for i, chapter := range comicsDetail.Chapter {
mediaContent := mediacontentmod.MediaContent{
ID: primitive.NewObjectID(),
MediaID: mediaTemp.ID,
EpisodeNumber: i + 1,
ListenPermission: mediaTemp.Permission,
Price: mediaTemp.Price,
Name: fmt.Sprintf("第%d话", i+1),
Text: "",
Md5: "",
VideoUrl: "",
UrlSet: make([]string, 0),
Status: 1,
IsActive: true,
UpdatedAct: manager,
CreatedAt: now,
UpdateTime: now,
HashId: chapter.Id,
}
mediaContent.Height, _ = strconv.Atoi(chapter.Content[0].H)
mediaContent.Weight, _ = strconv.Atoi(chapter.Content[0].W)
if chapter.Content != nil && len(chapter.Content) > 0 {
for _, content := range chapter.Content {
mediaContent.UrlSet = append(mediaContent.UrlSet, content.F)
}
}
contents = append(contents, mediaContent)
}
}
mediaTemp.CurrentEpisode = len(contents)
// 敏感词命中 → 强制下架(Status=0)
hit := vidser.CheckTextHits(terms, comicsDetail.Id, mediaTemp.Title, mediaTemp.Summary, "")
if hit != nil {
mediaTemp.Status = 0
log.Warn("syncOneComics sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
_, err = mediamod.Insert(nil, mediaTemp)
if err != nil {
log.Warn("syncOneComics InsertMedia failed", log.Any("media", mediaTemp.ID), log.E(err))
return nil, err
}
if len(contents) > 0 {
_ = mediacontentmod.InsetBatch(nil, contents)
}
return hit, nil
}
+207
View File
@@ -0,0 +1,207 @@
package laosijiser
import (
"91porn-server/common/laosiji"
"91porn-server/common/log"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/mediatagmod"
"91porn-server/web/service/vidser"
"errors"
"fmt"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func checkInsertMediaTag(names []string, typ int) (data map[string]primitive.ObjectID, err error) {
data, err = mediatagmod.FindOneTagByNames(names, typ)
if err != nil {
log.Error("checkInsertMediaTag FindOneTagByiNames failed", log.Any("tags", names), log.E(err))
return
}
insertTags := make([]mediatagmod.MediaTag, 0)
for _, tag := range names {
if _, ok := data[tag]; !ok {
insertTag := mediatagmod.MediaTag{
ID: primitive.NewObjectID(),
Type: typ,
Name: tag,
Active: true,
Sort: 0,
IsDiscovery: false,
CreatedAt: time.Now(),
}
insertTags = append(insertTags, insertTag)
data[tag] = insertTag.ID
}
}
if len(insertTags) > 0 {
err = mediatagmod.InsertMany(nil, insertTags)
if err != nil {
log.Warn("checkInsertMediaTag InsertMany failed", log.Any("tags", insertTags), log.E(err))
return
}
}
return
}
func syncCartoonList(movieDetails []laosiji.MovieDetailResp, tags map[string]struct{}, manager string, terms []string) ([]vidser.SensitiveHit, error) {
hits := make([]vidser.SensitiveHit, 0)
tagMaps := make(map[string]primitive.ObjectID)
var err error
if len(tags) > 0 {
tagList := make([]string, 0)
for k, _ := range tags {
tagList = append(tagList, k)
}
tagMaps, err = checkInsertMediaTag(tagList, 1)
if err != nil {
log.Error("syncCartoonList checkInsertVidTags failed", log.E(err))
return hits, err
}
}
failedIds := make([]string, 0)
for _, movieDetail := range movieDetails {
hit, oneErr := syncOneCartoon(movieDetail, tagMaps, manager, terms)
if oneErr != nil {
log.Warn("syncCartoonList syncOneCartoon failed", log.Any("id", movieDetail.Id), log.E(oneErr))
failedIds = append(failedIds, movieDetail.Id)
continue
}
if hit != nil {
hits = append(hits, *hit)
}
}
if len(failedIds) > 0 {
return hits, errors.New(fmt.Sprintf("需要同步%d部动漫,失败%d,失败Id[%s]", len(movieDetails), len(failedIds), strings.Join(failedIds, ",")))
}
return hits, nil
}
func syncOneCartoon(movieDetail laosiji.MovieDetailResp, tagMaps map[string]primitive.ObjectID, manager string, terms []string) (*vidser.SensitiveHit, error) {
now := time.Now()
mediaTemp, err := mediamod.GetListByLsjId(movieDetail.Id)
if err != nil {
log.Warn("syncOneCartoon GetListByLsjId failed", log.Any("id", movieDetail.Id), log.Any("err", err))
return nil, err
}
if !mediaTemp.ID.IsZero() {
return nil, syncExistingCartoonH265(mediaTemp.ID, movieDetail.Links, manager)
}
mediaTemp = mediamod.Media{
ID: primitive.NewObjectID(),
LsjId: movieDetail.Id,
Title: movieDetail.Name,
HorizontalCover: movieDetail.Img_x,
VerticalCover: movieDetail.Img_y,
Tags: make([]primitive.ObjectID, 0),
TagDetails: make([]mediamod.TagDetail, 0),
Summary: movieDetail.Description,
MediaType: mediamod.MediaTypeVideo,
Kind: 1,
PermissionIconHide: false,
Permission: 2,
Status: 1,
CreatedAt: now,
UpdateTime: now,
UpdatedAct: manager,
Anchor: movieDetail.Director,
}
mediaTemp.UpdateStatus, _ = strconv.Atoi(movieDetail.Update_status)
//if mediaTemp.UpdateStatus == 1 {
// mediaTemp.UpdatedDesc = fmt.Sprintf("已完结%d话", mediaTemp.TotalEpisode)
//} else {
// mediaTemp.UpdatedDesc = fmt.Sprintf("更新至%d话", mediaTemp.TotalEpisode)
//}
for _, tag := range movieDetail.Tags {
if v, ok := tagMaps[tag.Name]; ok {
mediaTemp.TagDetails = append(mediaTemp.TagDetails, mediamod.TagDetail{
Id: v,
Name: tag.Name,
})
mediaTemp.Tags = append(mediaTemp.Tags, v)
}
}
contents := make([]mediacontentmod.MediaContent, 0)
if len(movieDetail.Links) > 0 {
for i, chapter := range movieDetail.Links {
mediaContent := mediacontentmod.MediaContent{
ID: primitive.NewObjectID(),
MediaID: mediaTemp.ID,
EpisodeNumber: i + 1,
ListenPermission: mediaTemp.Permission,
Price: mediaTemp.Price,
Name: chapter.Name,
VideoUrl: chapter.M3u8_url,
H265Url: strings.TrimSpace(chapter.Hevc_m3u8_url),
UrlSet: make([]string, 0),
Status: 1,
IsActive: true,
UpdatedAct: manager,
CreatedAt: now,
UpdateTime: now,
HashId: chapter.Id,
}
contents = append(contents, mediaContent)
}
}
mediaTemp.CurrentEpisode = len(contents)
mediaTemp.TotalEpisode = mediaTemp.CurrentEpisode
// 敏感词命中 → 强制下架(Status=0)
hit := vidser.CheckTextHits(terms, movieDetail.Id, mediaTemp.Title, mediaTemp.Summary, "")
if hit != nil {
mediaTemp.Status = 0
log.Warn("syncOneCartoon sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
_, err = mediamod.Insert(nil, mediaTemp)
if err != nil {
log.Warn("syncOneCartoon InsertMedia failed", log.Any("media", mediaTemp.ID), log.E(err))
return nil, err
}
if len(contents) > 0 {
_ = mediacontentmod.InsetBatch(nil, contents)
}
return hit, nil
}
// syncExistingCartoonH265 允许重复导入老司机动漫时回填历史章节的 H.265 地址。
// 只接受上游非空地址,不改动现有 H.264 地址,也不会用空值覆盖已入库资源。
func syncExistingCartoonH265(mediaID primitive.ObjectID, links []laosiji.LinkInfo, manager string) error {
updatedIDs := make([]primitive.ObjectID, 0)
for _, chapter := range links {
h265URL := strings.TrimSpace(chapter.Hevc_m3u8_url)
if h265URL == "" || strings.TrimSpace(chapter.Id) == "" {
continue
}
content, err := mediacontentmod.GetInfoByHashId(mediaID, chapter.Id)
if err == mediacontentmod.MediaContentNotFound {
continue
}
if err != nil {
return err
}
if strings.TrimSpace(content.H265Url) == h265URL {
continue
}
if _, err = mediacontentmod.UpdateByID(nil, content.ID, map[string]interface{}{
"h265Url": h265URL,
"updatedAct": manager,
"updateTime": time.Now(),
}); err != nil {
return err
}
updatedIDs = append(updatedIDs, content.ID)
}
return nil
}
+314
View File
@@ -0,0 +1,314 @@
package laosijiser
import (
"91porn-server/common"
"91porn-server/common/crypt"
"91porn-server/common/httputil"
"91porn-server/common/laosiji"
"91porn-server/common/log"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/web/webg"
"context"
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func NovelSearch(ctx context.Context, param laosiji.NovelSearchListReq) (resp laosiji.NovelSearchListResp, err error) {
novelResp, err := laosiji.NovelSearch(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
resp = laosiji.NovelSearchListResp{
Data: make([]laosiji.NovelSearchInfo, 0),
Total: novelResp.Total,
Current_page: novelResp.Current_page,
Page_size: novelResp.Page_size,
Last_page: novelResp.Last_page,
}
//是否导入
taskIds := make([]string, 0)
for _, v := range novelResp.Data {
taskIds = append(taskIds, v.Id)
}
novelList, _ := mediamod.GetListByLsjIds(taskIds)
// 处理域名
for _, novelInfo := range novelResp.Data {
//novelInfo.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, novelInfo.Img)
novelInfo.Img = common.JoinUrlPath("laosiji", novelInfo.Img)
for _, novelIn := range novelList {
if novelIn.LsjId == novelInfo.Id {
novelInfo.IsAdd = true
continue
}
}
resp.Data = append(resp.Data, novelInfo)
}
return
}
func NovelDetail(ctx context.Context, param laosiji.NovelDetailReq) (resp laosiji.NovelDetailResp, err error) {
resp, err = laosiji.NovelDetail(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
// 处理域名
//resp.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Img)
resp.Img = common.JoinUrlPath("laosiji", resp.Img)
if resp.Chapter != nil && len(resp.Chapter) > 0 {
for i, cha := range resp.Chapter {
if len(cha.Img) > 0 {
//resp.Chapter[i].Img = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Chapter[i].Img)
resp.Chapter[i].Img = common.JoinUrlPath("laosiji", resp.Chapter[i].Img)
}
if len(cha.Content) > 0 {
if cha.Is_audio == "y" {
resp.Chapter[i].Content = common.JoinUrlPath("laosiji/m3m", resp.Chapter[i].Content)
} else {
resp.Chapter[i].Content = common.JoinUrlPath("laosiji", resp.Chapter[i].Content)
}
}
}
}
return
}
func NovelAddList(ctx context.Context, param laosiji.NovelAddListReq, manager string) (err error) {
if param.Ids == nil || len(param.Ids) <= 0 {
return
}
novelDetails := make([]laosiji.NovelDetailResp, 0)
for _, id := range param.Ids {
var novelDetail laosiji.NovelDetailResp
req := laosiji.NovelDetailReq{
Id: id,
}
novelDetail, err = laosiji.NovelDetail(ctx, req)
if err != nil {
log.Warn("NovelAddList laosiji.NovelDetail failed", log.Any("id", id), log.E(err))
continue
}
// 处理域名
//novelDetail.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, novelDetail.Img)
novelDetail.Img = common.JoinUrlPath("laosiji/", novelDetail.Img)
if novelDetail.Chapter != nil && len(novelDetail.Chapter) > 0 {
for i, cha := range novelDetail.Chapter {
if len(cha.Img) > 0 {
novelDetail.Chapter[i].Img = common.JoinUrlPath("laosiji/", novelDetail.Chapter[i].Img)
}
if len(cha.Content) > 0 {
if cha.Is_audio == "y" {
return errors.New("暂不支持有声小说!!")
//novelDetail.Chapter[i].Content = common.JoinUrlPath("laosiji/m3m", novelDetail.Chapter[i].Content)
} else {
novelDetail.Chapter[i].Content = common.JoinUrlPath("laosiji", novelDetail.Chapter[i].Content)
}
}
}
}
novelDetails = append(novelDetails, novelDetail)
}
if len(novelDetails) <= 0 {
log.Warn("NovelAddList laosiji.NovelDetailResp len is 0")
err = errors.New("laosiji.NovelDetailResp len is 0")
return
}
// 批量处理标签
tagMaps := make(map[string]primitive.ObjectID)
tagList := make([]string, 0) // 减少重复循环
tagList2 := make([]string, 0) // 减少重复循环
tagTmpMap := map[int]map[string]any{ // 区分类别和去重
3: make(map[string]any), // 文本
4: make(map[string]any), // 有声
}
for _, comicsDetail := range novelDetails {
for _, tag := range comicsDetail.Tags {
if len(comicsDetail.Chapter) > 0 && comicsDetail.Chapter[0].Is_audio == "y" {
if _, ok := tagTmpMap[4][tag.Name]; !ok {
tagTmpMap[4][tag.Name] = nil
tagList = append(tagList, tag.Name)
}
} else {
if _, ok := tagTmpMap[3][tag.Name]; !ok {
tagTmpMap[3][tag.Name] = nil
tagList2 = append(tagList, tag.Name)
}
}
}
}
if len(tagTmpMap[3]) > 0 {
tagMaps, err = checkInsertMediaTag(tagList, 3)
if err != nil {
log.Error("NovelAddList checkInsertMediaTag failed", log.E(err))
return err
}
}
if len(tagTmpMap[4]) > 0 {
tagMaps2, err := checkInsertMediaTag(tagList2, 4)
if err != nil {
log.Error("NovelAddList checkInsertMediaTag failed", log.E(err))
return err
}
for k, id := range tagMaps2 {
tagMaps[k] = id
}
}
failedIds := make([]string, 0)
wg := sync.WaitGroup{}
for index, _ := range novelDetails {
wg.Add(1)
common.GoParam(index, func(i int) {
defer wg.Done()
comicsDetail := novelDetails[i]
err = syncOneNovel(comicsDetail, tagMaps, manager)
if err != nil {
log.Warn("NovelAddList syncOneNovel failed", log.Any("id", comicsDetail.Id), log.E(err))
failedIds = append(failedIds, comicsDetail.Id)
}
})
}
wg.Wait()
if len(failedIds) > 0 {
err = errors.New(fmt.Sprintf("需要同步%d部小说,失败%d,失败Id[%s]", len(param.Ids), len(failedIds), strings.Join(failedIds, ",")))
}
return
}
func syncOneNovel(novelDetail laosiji.NovelDetailResp, tagMaps map[string]primitive.ObjectID, manager string) error {
now := time.Now()
mediaTemp, err := mediamod.GetListByLsjId(novelDetail.Id)
if err != nil {
log.Warn("syncOneNovel GetListByLsjId failed", log.Any("id", novelDetail.Id), log.Any("err", err))
return err
}
if !mediaTemp.ID.IsZero() {
return nil
}
mediaTemp = mediamod.Media{
ID: primitive.NewObjectID(),
LsjId: novelDetail.Id,
Title: novelDetail.Name,
HorizontalCover: novelDetail.Img,
VerticalCover: novelDetail.Img,
Tags: make([]primitive.ObjectID, 0),
TagDetails: make([]mediamod.TagDetail, 0),
Summary: novelDetail.Description,
FreeEpisode: 0,
CurrentEpisode: 0,
MediaType: mediamod.MediaTypeText,
Kind: 7,
Anchor: novelDetail.Author,
PermissionIconHide: false,
Permission: 2,
Price: 0,
ContentsPrice: 0,
Status: 1,
CreatedAt: now,
UpdateTime: now,
UpdatedAct: manager,
}
isAudo := false
mediaTemp.TotalEpisode, _ = strconv.Atoi(novelDetail.Chapter_count)
mediaTemp.UpdateStatus, _ = strconv.Atoi(novelDetail.Update_status)
mediaTemp.Price, _ = strconv.ParseInt(novelDetail.Money, 10, 64)
if mediaTemp.Price > 0 {
mediaTemp.Permission = 1
}
//if mediaTemp.UpdateStatus == 1 {
// mediaTemp.UpdatedDesc = fmt.Sprintf("已完结%d话", mediaTemp.TotalEpisode)
//} else {
// mediaTemp.UpdatedDesc = fmt.Sprintf("更新至%d话", mediaTemp.TotalEpisode)
//}
for _, tag := range novelDetail.Tags {
if v, ok := tagMaps[tag.Name]; ok {
mediaTemp.TagDetails = append(mediaTemp.TagDetails, mediamod.TagDetail{
Id: v,
Name: tag.Name,
})
mediaTemp.Tags = append(mediaTemp.Tags, v)
}
}
contents := make([]mediacontentmod.MediaContent, 0)
if len(novelDetail.Chapter) > 0 {
for i, chapter := range novelDetail.Chapter {
mediaContent := mediacontentmod.MediaContent{
ID: primitive.NewObjectID(),
MediaID: mediaTemp.ID,
EpisodeNumber: i + 1,
ListenPermission: mediaTemp.Permission,
Price: mediaTemp.Price,
Name: fmt.Sprintf("第%d章", i+1),
UrlSet: make([]string, 0),
Status: 1,
IsActive: true,
Author: mediaTemp.Anchor,
IsDelete: false,
UpdatedAct: manager,
CreatedAt: now,
UpdateTime: now,
HashId: chapter.Id,
}
if chapter.Is_audio == "y" {
mediaContent.AudioUrl = chapter.Content
isAudo = true
} else {
content, err := textNovelContent(chapter.Content)
if err != nil {
log.Warn("syncOneNovel textNovelContent failed", log.Any("err", err))
return err
}
mediaContent.Text = content
}
contents = append(contents, mediaContent)
}
}
if isAudo {
mediaTemp.MediaSubType = 1
}
_, err = mediamod.Insert(nil, mediaTemp)
if err != nil {
log.Warn("syncOneNovel InsertMedia failed", log.Any("media", mediaTemp.ID), log.E(err))
return err
}
if len(contents) > 0 {
_ = mediacontentmod.InsetBatch(nil, contents)
}
return nil
}
func textNovelContent(url string) (string, error) {
decKey := "525202f9149e061d"
path := fmt.Sprintf("%s/%s", webg.Conf.URL.LaoSiJiTSCdn, url)
resp, err := httputil.DefaultClientGet(path, make(map[string]string))
if err != nil {
log.Warn("textNovelContent http client get failed", log.Any("url", path), log.Any("err", err))
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Warn("textNovelContent read response body failed", log.Any("err", err))
return "", err
}
b := crypt.AESECBDecrypt(body, []byte(decKey))
return string(b), nil
}
+34
View File
@@ -0,0 +1,34 @@
package laosijiser
import (
"91porn-server/common/crypt"
"91porn-server/common/httputil"
"91porn-server/common/log"
"fmt"
"io/ioutil"
"testing"
)
func TestNovelDetail(t *testing.T) {
url := "laosiji/media/novel/cbd/cbd5b05ea5677797958895694211582c.bnc"
key := "525202f9149e061d"
domain := "https://tssdaa.hlvwcn.cn"
//req, err := http.NewRequest("GET", url, nil)
//if err != nil {
// t.Fatal(err)
// return
//}
resp, err := httputil.ClientGet(30, fmt.Sprintf("%s/%s", domain, url), make(map[string]string))
if err != nil {
log.Warn("http client get failed", log.Any("err", err))
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Warn("read response body failed", log.Any("err", err))
return
}
b := crypt.AESECBDecrypt(body, []byte(key))
log.Info(string(b))
}
+229
View File
@@ -0,0 +1,229 @@
package laosijiser
import (
"91porn-server/common"
"91porn-server/common/laosiji"
"91porn-server/common/log"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"91porn-server/web/service/vidser"
"context"
"errors"
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func PostSearch(ctx context.Context, param laosiji.PostSearchListReq) (resp laosiji.PostSearchListResp, err error) {
postResp, err := laosiji.PostSearch(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
resp = laosiji.PostSearchListResp{
Data: make([]laosiji.PostSearchInfo, 0),
Total: postResp.Total,
Current_page: postResp.Current_page,
Page_size: postResp.Page_size,
Last_page: postResp.Last_page,
}
//是否导入
taskIds := make([]string, 0)
for _, v := range postResp.Data {
taskIds = append(taskIds, v.Id)
}
postList, _ := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": taskIds}})
// 处理域名
for _, postInfo := range postResp.Data {
//postInfo.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, postInfo.Img)
postInfo.Img = common.JoinUrlPath("laosiji", postInfo.Img)
for _, postIn := range postList {
if postIn.SourceID == postInfo.Id {
postInfo.IsAdd = true
continue
}
}
resp.Data = append(resp.Data, postInfo)
}
return resp, nil
}
func PostDetail(ctx context.Context, param laosiji.PostDetailReq) (resp laosiji.PostDetailResp, err error) {
resp, err = laosiji.PostDetail(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
// 处理域名
//resp.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Img)
resp.Img = common.JoinUrlPath("laosiji", resp.Img)
if resp.Files != nil && len(resp.Files) > 0 {
for i, file := range resp.Files {
if len(file.Image) > 0 {
//resp.Files[i].Image = common.JoinUrlPath(laosiji.IMAGEYUAN, file.Image)
resp.Files[i].Image = common.JoinUrlPath("laosiji", file.Image)
}
if len(file.Video_link) > 0 {
resp.Files[i].Video_link = common.JoinUrlPath(laosiji.APIUrl+"/m3m", file.Video_link)
}
}
}
return
}
func PostAddList(ctx context.Context, param laosiji.PostAddListReq, manager string) (hits []vidser.SensitiveHit, err error) {
if param.Ids == nil || len(param.Ids) <= 0 {
return
}
// 批量查询所有详情
postDetails := make([]laosiji.PostDetailResp, 0)
for _, id := range param.Ids {
var postDetail laosiji.PostDetailResp
req := laosiji.PostDetailReq{
Id: id,
}
postDetail, err = laosiji.PostDetail(ctx, req)
if err != nil {
log.Warn("PostAddList laosiji.PostDetail failed", log.Any("id", id), log.E(err))
continue
}
//postDetail.Img = common.JoinUrlPath(laosiji.IMAGEYUAN, postDetail.Img)
postDetail.Img = common.JoinUrlPath("laosiji", postDetail.Img)
if postDetail.Files != nil && len(postDetail.Files) > 0 {
for i, file := range postDetail.Files {
if len(file.Image) > 0 {
postDetail.Files[i].Image = common.JoinUrlPath("laosiji", file.Image)
}
if len(file.Video_link) > 0 {
postDetail.Files[i].Video_link = common.JoinUrlPath("laosiji/m3m/", file.Video_link)
}
}
}
postDetails = append(postDetails, postDetail)
}
if len(postDetails) <= 0 {
log.Warn("PostAddList laosiji.PostDetailResp len is 0")
err = errors.New("laosiji.PostDetailResp len is 0")
return
}
users, err := usermod.FindMany(bson.M{"uid": bson.M{"$gt": 300000}}, &options.FindOptions{}, 0, 300)
if err != nil {
log.Warn("PostAddList FindManyUser failed", log.E(err))
return
}
terms := vidser.LoadEnabledSensitiveTerms()
hits = make([]vidser.SensitiveHit, 0)
failedIds := make([]string, 0)
for _, postDetail := range postDetails {
hit, oneErr := addOnePost(postDetail, nil, users, manager, terms)
if oneErr != nil {
log.Warn("PostAddList addOnePost failed", log.Any("id", postDetail.Id), log.E(oneErr))
failedIds = append(failedIds, postDetail.Id)
continue
}
if hit != nil {
hits = append(hits, *hit)
}
}
if len(failedIds) > 0 {
err = errors.New(fmt.Sprintf("需要同步%d个帖子,失败%d,失败Id[%s]", len(param.Ids), len(failedIds), strings.Join(failedIds, ",")))
}
return
}
func addOnePost(postDetail laosiji.PostDetailResp, tagMaps map[string]primitive.ObjectID, upUsers []*usermod.User, manager string, terms []string) (*vidser.SensitiveHit, error) {
//
// 有的帖子是图文节点 前端格式不支持 这里屏蔽
//
if len(postDetail.Files) == 0 {
return nil, nil
}
now := time.Now()
postList, err := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": []string{postDetail.Id}}})
if err != nil {
log.Warn("addOnePost GetList failed", log.Any("id", postDetail.Id), log.E(err))
return nil, err
}
if postList != nil && len(postList) > 0 {
return nil, nil
}
postInfo := vidmod.VideoModel{
ID: primitive.NewObjectID(),
NewsType: vidmod.COVER,
Title: postDetail.Title,
Content: postDetail.Content,
Tags: make([]primitive.ObjectID, 0),
MimeType: "video/mp4",
Cover: postDetail.Img,
CoverThumb: postDetail.Img,
SeriesCover: make([]string, 0),
Via: "laosiji",
Status: vidmod.CheckPass,
Location: primitive.ObjectID{},
NewUpdatedAt: "laosiji",
ReviewAt: now,
CreatedAt: now,
UpdatedAt: now,
ReviewAccount: manager,
SourceID: postDetail.Id,
VerticalCover: postDetail.Img,
}
postInfo.SeriesNum, _ = strconv.Atoi(postDetail.Img_count)
postInfo.Coins, _ = strconv.ParseInt(postDetail.Money, 10, 64)
uIndex := rand.Intn(len(upUsers))
u := upUsers[uIndex]
postInfo.PublisherID = u.UID
//if postInfo.Coins > 0 {
// postInfo.Permission = 1
//} else {
// postInfo.Permission = 2
// postInfo.FreeArea = true
// postInfo.FreeAreaDate = now
//}
//节点 处理
if postDetail.Files != nil && len(postDetail.Files) > 0 {
for _, file := range postDetail.Files {
if file.Type == "image" {
if len(file.Image) > 0 {
postInfo.SeriesCover = append(postInfo.SeriesCover, file.Image)
}
}
if file.Type == "video" {
postInfo.SourceURL = file.Video_link
postInfo.PlayTime = 1
postInfo.Filename = file.Tips
postInfo.SourceID = postDetail.Id
}
}
}
if postDetail.Rich_content != nil && len(postDetail.Rich_content) > 0 {
for _, file := range postDetail.Rich_content {
postInfo.RichText += fmt.Sprintf("%s\n", file)
}
}
// 敏感词命中 → 强制为待审核(WaitingCheck=0),写库后由后台逐条告警
hit := vidser.CheckTextHits(terms, postDetail.Id, postInfo.Title, postInfo.Content, postInfo.RichText)
if hit != nil {
postInfo.Status = vidmod.WaitingCheck
log.Warn("addOnePost sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
if _, err = vidmod.InsertBase(postInfo); err != nil {
return nil, err
}
return hit, nil
}
+338
View File
@@ -0,0 +1,338 @@
package laosijiser
import (
"91porn-server/common"
"91porn-server/common/constant/redisconst"
"91porn-server/common/laosiji"
"91porn-server/common/log"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/tagmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"91porn-server/web/service/vidser"
"91porn-server/web/webg"
"context"
"errors"
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
func MovieSearch(ctx context.Context, param laosiji.MovieSearchReq) (resp laosiji.MovieSearchResp, err error) {
var yuanresp laosiji.MovieSearchResp
yuanresp, err = laosiji.MovieSearch(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
//是否导入
taskIds := make([]string, 0)
for _, v := range yuanresp.Data {
taskIds = append(taskIds, v.Id)
}
resp = laosiji.MovieSearchResp{
Data: make([]laosiji.MovieInfo, 0),
Total: yuanresp.Total,
Current_page: yuanresp.Current_page,
Page_size: yuanresp.Page_size,
Last_page: yuanresp.Last_page,
}
if param.Position == "guochan" || param.Position == "av" || param.Position == "douyin" || param.Position == "dark" {
videoList, _ := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": taskIds}})
// 处理域名
for _, movieInfo := range yuanresp.Data {
//movieInfo.Img_x = common.JoinUrlPath(laosiji.IMAGEYUAN, movieInfo.Img_x)
//movieInfo.Img_y = common.JoinUrlPath(laosiji.IMAGEYUAN, movieInfo.Img_y)
movieInfo.Img_x = common.JoinUrlPath("laosiji", movieInfo.Img_x)
movieInfo.Img_y = common.JoinUrlPath("laosiji", movieInfo.Img_y)
for _, videoInfo := range videoList {
if videoInfo.SourceID == movieInfo.Id {
movieInfo.IsAdd = true
continue
}
}
resp.Data = append(resp.Data, movieInfo)
}
} else if param.Position == "cartoon" {
mediaList, _ := mediamod.GetListByLsjIds(taskIds)
for _, movieInfo := range yuanresp.Data {
//movieInfo.Img_x = common.JoinUrlPath(laosiji.IMAGEYUAN, movieInfo.Img_x)
//movieInfo.Img_y = common.JoinUrlPath(laosiji.IMAGEYUAN, movieInfo.Img_y)
movieInfo.Img_x = common.JoinUrlPath("laosiji", movieInfo.Img_x)
movieInfo.Img_y = common.JoinUrlPath("laosiji", movieInfo.Img_y)
for _, videoInfo := range mediaList {
if videoInfo.LsjId == movieInfo.Id {
movieInfo.IsAdd = true
continue
}
}
resp.Data = append(resp.Data, movieInfo)
}
} else {
err = errors.New("该资源暂不支持")
}
return resp, nil
}
func MovieDetail(ctx context.Context, param laosiji.MovieDetailReq) (resp laosiji.MovieDetailResp, err error) {
resp, err = laosiji.MovieDetail(ctx, param)
if err != nil {
log.Error("MovieSearch", log.E(err))
return
}
// 处理域名
//resp.Img_x = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Img_x)
//resp.Img_y = common.JoinUrlPath(laosiji.IMAGEYUAN, resp.Img_y)
resp.Img_x = common.JoinUrlPath("laosiji", resp.Img_x)
resp.Img_y = common.JoinUrlPath("laosiji", resp.Img_y)
for key, value := range resp.Preview_images {
//resp.Preview_images[key] = common.JoinUrlPath(laosiji.IMAGEYUAN, value)
resp.Preview_images[key] = common.JoinUrlPath("laosiji", value)
}
for key, value := range resp.Links {
resp.Links[key].M3u8_url = laosiji.MovieM3u8OriginURL(value.M3u8_url)
resp.Links[key].Preview_m3u8_url = laosiji.MovieM3u8OriginURL(value.Preview_m3u8_url)
resp.Links[key].Hevc_m3u8_url = laosiji.MovieM3u8OriginURL(value.Hevc_m3u8_url)
}
return resp, nil
}
func MovieAddList(ctx context.Context, param laosiji.MovieAddListReq, manager string) ([]vidser.SensitiveHit, error) {
if param.Ids == nil || len(param.Ids) <= 0 {
return nil, nil
}
movieDetails := make([]laosiji.MovieDetailResp, 0)
for _, id := range param.Ids {
var movieDetail laosiji.MovieDetailResp
req := laosiji.MovieDetailReq{
Id: id,
}
movieDetail, err := laosiji.MovieDetail(ctx, req)
if err != nil {
log.Warn("MovieAddList laosiji.MovieDetail failed", log.Any("id", id), log.E(err))
continue
}
for i, linkInfo := range movieDetail.Links {
linkInfo.M3u8_url = laosiji.MovieM3u8SourcePath(linkInfo.M3u8_url)
linkInfo.Preview_m3u8_url = laosiji.MovieM3u8SourcePath(linkInfo.Preview_m3u8_url)
linkInfo.Hevc_m3u8_url = laosiji.MovieM3u8SourcePath(linkInfo.Hevc_m3u8_url)
movieDetail.Links[i] = linkInfo
}
if len(movieDetail.Img_y) > 0 {
movieDetail.Img_y = common.JoinUrlPath("laosiji/", movieDetail.Img_y)
}
if len(movieDetail.Img_x) > 0 {
movieDetail.Img_x = common.JoinUrlPath("laosiji/", movieDetail.Img_x)
}
movieDetails = append(movieDetails, movieDetail)
}
if len(movieDetails) <= 0 {
log.Warn("MovieAddList laosiji.MovieDetailResp len is 0")
return nil, errors.New("laosiji.MovieDetailResp len is 0")
}
// 批量处理标签
tags := make(map[string]struct{})
for _, comicsDetail := range movieDetails {
if comicsDetail.Tags != nil && len(comicsDetail.Tags) > 0 {
for _, tag := range comicsDetail.Tags {
tags[tag.Name] = struct{}{}
}
}
}
terms := vidser.LoadEnabledSensitiveTerms()
hits := make([]vidser.SensitiveHit, 0)
if param.Position == "cartoon" {
return syncCartoonList(movieDetails, tags, manager, terms)
} else if param.Position == "av" || param.Position == "guochan" || param.Position == "douyin" || param.Position == "dark" {
tagMaps := make(map[string]primitive.ObjectID)
var err error
if len(tags) > 0 {
tagList := make([]string, 0)
for k, _ := range tags {
tagList = append(tagList, k)
}
tagMaps, err = checkInsertVidTags(tagList)
if err != nil {
log.Error("MovieAddList checkInsertVidTags failed", log.E(err))
return hits, err
}
}
users, err := usermod.FindMany(bson.M{"uid": bson.M{"$gt": 300000}}, &options.FindOptions{}, 0, 300)
if err != nil {
log.Warn("PostAddList FindManyUser failed", log.E(err))
return hits, err
}
failedIds := make([]string, 0)
for _, comicsDetail := range movieDetails {
hit, oneErr := syncOneMovie(comicsDetail, tagMaps, users, manager, terms)
if oneErr != nil {
log.Warn("MovieAddList syncOneMovie failed", log.Any("id", comicsDetail.Id), log.E(oneErr))
failedIds = append(failedIds, comicsDetail.Id)
continue
}
if hit != nil {
hits = append(hits, *hit)
}
}
if len(failedIds) > 0 {
return hits, errors.New(fmt.Sprintf("需要同步%d部资源,失败%d,失败Id[%s]", len(param.Ids), len(failedIds), strings.Join(failedIds, ",")))
}
return hits, nil
}
return hits, errors.New("该资源暂不支持")
}
func syncOneMovie(movieDetail laosiji.MovieDetailResp, tags map[string]primitive.ObjectID, upUsers []*usermod.User, manager string, terms []string) (*vidser.SensitiveHit, error) {
now := time.Now()
postList, err := vidmod.GetList(bson.M{"sourceID": bson.M{"$in": []string{movieDetail.Id}}})
if err != nil {
log.Warn("syncOneMovie GetList failed", log.Any("id", movieDetail.Id), log.E(err))
return nil, err
}
if postList != nil && len(postList) > 0 {
h265URL := movieH265SourceURL(movieDetail.Links)
if h265URL == "" {
return nil, nil
}
video := postList[0]
if err = vidmod.MarkH265Success(video.ID, h265URL); err != nil {
return nil, err
}
if webg.Redis != nil {
cacheKey := fmt.Sprintf(redisconst.VideoInfoKey(), video.ID.Hex())
if _, err = webg.Redis.Del(cacheKey); err != nil {
log.Warn("syncOneMovie clear video cache failed",
log.Any("id", video.ID),
log.Any("cacheKey", cacheKey),
log.E(err))
return nil, err
}
}
return nil, nil
}
vidInfo := vidmod.VideoModel{
ID: primitive.NewObjectID(),
Title: movieDetail.Name,
Content: movieDetail.Description,
Tags: make([]primitive.ObjectID, 0),
MimeType: "video/mp4",
SeriesCover: make([]string, 0),
Via: "laosiji",
Status: vidmod.CheckPass,
Location: primitive.ObjectID{},
Actor: movieDetail.Actor,
NewUpdatedAt: "laosiji",
ReviewAt: now,
CreatedAt: now,
UpdatedAt: now,
ReviewAccount: manager,
VerticalCover: movieDetail.Img_y,
}
if movieDetail.Img_type == "short" && len(movieDetail.Img_y) > 0 {
vidInfo.NewsType = vidmod.SHORT
vidInfo.Cover = movieDetail.Img_y
vidInfo.CoverThumb = movieDetail.Img_y
vidInfo.Direction = "vertical"
} else {
vidInfo.NewsType = vidmod.SP
vidInfo.Cover = movieDetail.Img_x
vidInfo.CoverThumb = movieDetail.Img_x
vidInfo.Direction = "horizontal"
}
playTime, _ := strconv.Atoi(movieDetail.Duration)
vidInfo.PlayTime = uint(playTime)
uIndex := rand.Intn(len(upUsers))
u := upUsers[uIndex]
vidInfo.PublisherID = u.UID
if movieDetail.Links != nil && len(movieDetail.Links) > 0 {
vidInfo.SourceURL = movieDetail.Links[0].M3u8_url
vidInfo.Filename = movieDetail.Links[0].Name
vidInfo.PreviewURL = movieDetail.Links[0].Preview_m3u8_url
vidInfo.H265Url = movieH265SourceURL(movieDetail.Links)
if vidInfo.H265Url != "" {
vidInfo.H265Status = vidmod.H265StatusSuccess
}
}
vidInfo.SourceID = movieDetail.Id
if movieDetail.Tags != nil && len(movieDetail.Tags) > 0 {
for _, tag := range movieDetail.Tags {
if k, ok := tags[tag.Name]; ok {
vidInfo.Tags = append(vidInfo.Tags, k)
}
}
}
// 敏感词命中 → 强制为待审核(WaitingCheck=0)
hit := vidser.CheckTextHits(terms, movieDetail.Id, vidInfo.Title, vidInfo.Content, "")
if hit != nil {
vidInfo.Status = vidmod.WaitingCheck
log.Warn("syncOneMovie sensitive hit, forced offline",
log.Any("sourceId", hit.SourceID),
log.Any("title", hit.Title),
log.Any("detail", hit.Detail))
}
if vidInfo.H265Url == "" && vidInfo.SourceURL != "" && vidInfo.Status == vidmod.CheckPass && vidInfo.NewsType == vidmod.SP {
vidInfo.H265Status = vidmod.H265StatusQueued
vidInfo.H265QueuedAt = now
}
if _, err = vidmod.InsertBase(vidInfo); err != nil {
return nil, err
}
return hit, nil
}
// movieH265SourceURL 返回首个播放链接对应的 H.265 地址。
// syncOneMovie 的 H.264 也固定使用 links[0],不能拿后续分集的 H.265 与首集混用。
func movieH265SourceURL(links []laosiji.LinkInfo) string {
if len(links) == 0 {
return ""
}
return laosiji.MovieM3u8SourcePath(links[0].Hevc_m3u8_url)
}
func checkInsertVidTags(names []string) (data map[string]vidmod.ObjectID, err error) {
data = make(map[string]vidmod.ObjectID)
tags, err := tagmod.FindOneTagByNames(names)
if err != nil {
log.Warn("checkInsertVidTags tagmod.FindOneTagByNames", log.E(err), log.Any("name", names))
return
}
for _, tag := range tags {
data[tag.TagName] = tag.ID
}
insertTags := make([]tagmod.Tag, 0)
for _, tag := range tags {
if _, ok := data[tag.TagName]; !ok {
insertTag := tagmod.Tag{
ID: primitive.NewObjectID(),
TagName: tag.TagName,
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
insertTags = append(insertTags, insertTag)
data[tag.TagName] = insertTag.ID
}
}
if len(insertTags) > 0 {
err = tagmod.InsertMany(insertTags)
if err != nil {
log.Warn("checkInsertVidTags InsertMany failed", log.Any("tags", insertTags), log.E(err))
return
}
}
return
}
+53
View File
@@ -0,0 +1,53 @@
package laosijiser
import (
"testing"
"91porn-server/common/laosiji"
)
func TestMovieH265SourceURL(t *testing.T) {
tests := []struct {
name string
links []laosiji.LinkInfo
want string
}{
{
name: "empty",
links: []laosiji.LinkInfo{
{Hevc_m3u8_url: " "},
},
},
{
name: "does not mix a later episode with first H264 link",
links: []laosiji.LinkInfo{
{Hevc_m3u8_url: ""},
{Hevc_m3u8_url: "https://cdn.example.com/m3m/movie/hevc.m3u8?token=ignored"},
{Hevc_m3u8_url: "other.m3u8"},
},
},
{
name: "first link normalized",
links: []laosiji.LinkInfo{
{Hevc_m3u8_url: "https://cdn.example.com/m3m/movie/hevc.m3u8?token=ignored"},
{Hevc_m3u8_url: "other.m3u8"},
},
want: "laosiji/m3m/movie/hevc.m3u8",
},
{
name: "already normalized",
links: []laosiji.LinkInfo{
{Hevc_m3u8_url: "laosiji/rk130/hevc/movie/index.m3u8"},
},
want: "laosiji/rk130/hevc/movie/index.m3u8",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := movieH265SourceURL(test.links); got != test.want {
t.Fatalf("movieH265SourceURL() = %q, want %q", got, test.want)
}
})
}
}
+43
View File
@@ -0,0 +1,43 @@
package locationser
import (
"91porn-server/models/v/locmod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// CityPages CityPages
func CityPages(city string, page, size uint64) (locmod.CityPageResp, error) {
var resp locmod.CityPageResp
lModel, total, err := locmod.GetLocationList(city, page, size)
if err != nil {
return resp, err
}
lmodelLen := len(lModel)
tids := make([]primitive.ObjectID, lmodelLen)
for i, l := range lModel {
tids[i] = l.ID
}
mPlayCnt, err := vidmod.GetCityPlayCount2Map(tids)
if err != nil {
return resp, err
}
mVideosCnt, _ := vidmod.GetCityPublishedVideoCount2Map(tids)
resp.List = make([]locmod.LocInfo, lmodelLen)
for i, l := range lModel {
resp.List[i] = locmod.LocInfo{
ID: l.ID.Hex(),
City: l.City,
Cover: l.Cover,
Visit: l.Visit,
FakeVisit: l.FakeVisit,
SortKey: l.SortKey,
CreatedAt: l.CreatedAt,
VideoCount: mVideosCnt[l.ID],
PlayCount: mPlayCnt[l.ID],
}
}
resp.Total = int(total)
return resp, nil
}
+22
View File
@@ -0,0 +1,22 @@
package logser
import (
"91porn-server/common/stderr"
"91porn-server/models/v/txnmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// MAP 别名
type MAP map[string]interface{}
type ObjectID = primitive.ObjectID
// GetRechargeGoldLog 获取充值金币日志
func GetRechargeGoldLog(cond MAP, page, size uint64) (code stderr.Code, data interface{}) {
infos, _, total, err := txnmod.GetCoinLogs(page, size, cond)
if err != nil {
return stderr.ErrDbQueryError, nil
}
return stderr.Success, txnmod.CoinLogResp{Logs: infos, Total: total}
}
@@ -0,0 +1,121 @@
package mediacontentser
import (
"testing"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/vidmod"
)
func TestValidateDramaEpisodeState(t *testing.T) {
tests := []struct {
name string
episodeNumber int
listenPermission int
price int64
videoURL string
active bool
status int
wantErr bool
}{
{name: "free", episodeNumber: 1, listenPermission: 2, videoURL: "1.m3u8", active: true, status: 1},
{name: "coin", episodeNumber: 2, listenPermission: 1, price: 30, videoURL: "2.m3u8", active: true, status: 1},
{name: "free with price", episodeNumber: 1, listenPermission: 2, price: 30, wantErr: true},
{name: "coin without price", episodeNumber: 2, listenPermission: 1, wantErr: true},
{name: "active while transcoding", episodeNumber: 1, listenPermission: 2, videoURL: "1.m3u8", active: true, status: 0, wantErr: true},
{name: "active without resource", episodeNumber: 1, listenPermission: 2, active: true, status: 1, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateDramaEpisodeState(tt.episodeNumber, tt.listenPermission, tt.price, "第1集", tt.videoURL, "", tt.active, tt.status)
if gotErr := err != nil; gotErr != tt.wantErr {
t.Fatalf("validateDramaEpisodeState() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestValidateDramaEpisodeStateAllowsNonContinuousFreeEpisodes(t *testing.T) {
episodes := []struct {
episodeNumber int
listenPermission int
price int64
}{
{episodeNumber: 2, listenPermission: 1, price: 30},
{episodeNumber: 3, listenPermission: 2, price: 0},
}
for _, episode := range episodes {
if err := validateDramaEpisodeState(
episode.episodeNumber,
episode.listenPermission,
episode.price,
"剧集",
"episode.m3u8",
"",
true,
1,
); err != nil {
t.Fatalf("episode %d validation failed: %v", episode.episodeNumber, err)
}
}
}
func TestFileStatusToEpisodeStatus(t *testing.T) {
tests := map[string]int{
vidmod.Converting: 0,
vidmod.ConvertCompleted: 1,
vidmod.Completed: 1,
vidmod.ConvertError: 2,
vidmod.MergeError: 2,
}
for input, want := range tests {
if got := fileStatusToEpisodeStatus(input); got != want {
t.Fatalf("fileStatusToEpisodeStatus(%q) = %d, want %d", input, got, want)
}
}
}
func TestWebBatchUpdateReqValidatedIDs(t *testing.T) {
id := "507f1f77bcf86cd799439011"
price := int64(30)
permission := 1
freeEpisode := 1
req := WebBatchUpdateReq{
IDS: []string{id, id},
Price: &price,
Permission: &permission,
FreeEpisode: &freeEpisode,
}
ids, err := req.ValidatedIDs()
if err != nil {
t.Fatalf("ValidatedIDs() error: %v", err)
}
if len(ids) != 1 {
t.Fatalf("ValidatedIDs() length = %d, want 1", len(ids))
}
if _, err = (&WebBatchUpdateReq{IDS: []string{id}}).ValidatedIDs(); err == nil {
t.Fatal("request without update fields unexpectedly accepted")
}
req.IDS = []string{"bad-id"}
if _, err = req.ValidatedIDs(); err == nil {
t.Fatal("invalid ObjectID unexpectedly accepted")
}
}
func TestWebBatchUpdateReqAppliesPricingWithoutIsActive(t *testing.T) {
price := int64(30)
permission := 1
req := WebBatchUpdateReq{Price: &price, Permission: &permission}
content := mediacontentmod.MediaContent{IsActive: true, Price: 10, ListenPermission: 2}
next := req.applyContentFields(content)
if !next.IsActive {
t.Fatal("isActive changed when omitted")
}
if next.Price != price {
t.Fatalf("price = %d, want %d", next.Price, price)
}
if next.ListenPermission != permission {
t.Fatalf("listenPermission = %d, want %d", next.ListenPermission, permission)
}
}
+723
View File
@@ -0,0 +1,723 @@
package mediacontentser
import (
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/vidmod"
"91porn-server/web/service/updownloadser"
"errors"
"fmt"
"path/filepath"
"strings"
"time"
"go.mongodb.org/mongo-driver/mongo/options"
"91porn-server/models/v/mediacontentmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
ID *string `form:"id" bson:"id,omitempty"` // ID
Name *string `form:"name" bson:"name,omitempty"` // 名称
Type *int `form:"type" bson:"type,omitempty"` // 类型 1:动漫,2:漫画
Status *int `form:"status" bson:"status,omitempty"` // 状态
IsActive *bool `form:"isActive" json:"isActive"` // 是否激活
MID *string `form:"mId" json:"mId" bson:"mId" binding:"required"` // 媒体资源ID
PageNumber uint64 `form:"pageNumber" json:"pageNumber" binding:"required,min=1"` // 当前页
PageSize uint64 `form:"pageSize" json:"pageSize" binding:"required,min=1,max=100"` // 每页条数
}
type WebListRes struct {
Total int64 `json:"total"`
List []*mediacontentmod.MediaContent `json:"list"`
}
func (q *WebListReq) Filter() bson.M {
var query = bson.M{}
if q.Name != nil {
query["name"] = primitive.Regex{Pattern: *q.Name, Options: "i"}
}
if q.Status != nil {
query["status"] = *q.Status
}
if q.MID != nil {
mid, _ := primitive.ObjectIDFromHex(*q.MID)
query["mediaId"] = mid
}
if q.Type != nil {
query["type"] = *q.Type
}
if q.IsActive != nil {
query["isActive"] = *q.IsActive
}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
query["_id"] = id
}
query["isDelete"] = false
return query
}
func (q *WebListReq) Options() *options.FindOptions {
return options.Find().SetSkip(int64((q.PageNumber - 1) * q.PageSize)).SetLimit(int64(q.PageSize)).SetSort(bson.M{"episodeNumber": 1})
}
// GetList 获取列表
func (q *WebListReq) GetList() (res *WebListRes, err error) {
var out = &WebListRes{}
count, err := mediacontentmod.QueryAllCount(q.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*mediacontentmod.MediaContent{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = mediacontentmod.QueryAllList(q.Filter(), q.Options())
if err != nil {
log.Error(fmt.Sprintf("mediacontentmod queryAllList error:%+v:", err))
return nil, err
}
return out, nil
}
type WebCreateReq struct {
MediaID primitive.ObjectID `json:"mediaId" form:"mediaId" binding:"required"` // 媒体资源ID
MediaType string `json:"mediaType" form:"mediaType" binding:"required"` // 媒体类型
EpisodeNumber uint `json:"episodeNumber" form:"episodeNumber" ` // 第几集
ListenPermission int `json:"listenPermission" form:"listenPermission" ` // 收听权限 0:会员 1:金币购买 2:免费
Price int64 `json:"price" form:"price" ` // 购买价格
Cover string `json:"cover" form:"cover"` //封面
Name string `json:"name" form:"name" ` // 章节名
Text string `json:"text" form:"text" ` // 内容(小说专用)
Md5 string `json:"md5" form:"md5" ` // 媒体md5
SourceID string `json:"sourceID" form:"sourceID"` // 视频唯一id,对应文件服
VideoUrl string `json:"videoUrl" form:"videoUrl" ` // 视频地址
AudioUrl string `json:"audioUrl" form:"audioUrl"` // 有声小说地址
UrlSet []string `json:"urlSet" form:"urlSet"` // 地址集(多个资源地址用 例如漫画)
Filename string `json:"filename" form:"filename"` // 文件名称
IsActive bool `json:"isActive" form:"isActive" ` // 是否激活
PreviewEnabled bool `json:"previewEnabled" form:"previewEnabled"` // 是否允许试看
PreviewStart int `json:"previewStart" form:"previewStart"` // 试看起始时间(秒)
PreviewSeconds int `json:"previewSeconds" form:"previewSeconds"` // 试看时长(秒)
}
const defaultDramaPreviewSeconds = 60
func normalizeDramaPreview(enabled bool, start, seconds int, playTime uint) (bool, int, int, error) {
if !enabled {
return false, 0, 0, nil
}
if start < 0 {
return false, 0, 0, errors.New("试看起始时间不能小于0")
}
if seconds < 0 {
return false, 0, 0, errors.New("试看时长不能小于0")
}
if seconds == 0 {
seconds = defaultDramaPreviewSeconds
}
if playTime > 0 && uint64(start)+uint64(seconds) > uint64(playTime) {
return false, 0, 0, errors.New("试看起始时间与时长之和不能超过视频总时长")
}
return true, start, seconds, nil
}
func validateDramaEpisodeState(episodeNumber int, listenPermission int, price int64, name, videoURL, h265URL string, active bool, status int) error {
if episodeNumber < 1 {
return errors.New("剧集编号必须大于0")
}
if strings.TrimSpace(name) == "" {
return errors.New("剧集名称不能为空")
}
if listenPermission != 1 && listenPermission != 2 {
return errors.New("短剧剧集listenPermission只支持1或2")
}
if listenPermission == 2 && price != 0 {
return errors.New("免费剧集价格必须为0")
}
if listenPermission == 1 && price <= 0 {
return errors.New("金币剧集价格必须大于0")
}
if active {
if status != 1 {
return errors.New("视频仍在转码,暂不能上架")
}
if strings.TrimSpace(videoURL) == "" && strings.TrimSpace(h265URL) == "" {
return errors.New("剧集没有播放资源,暂不能上架")
}
}
return nil
}
func ensureEpisodeNumberUnique(mediaID primitive.ObjectID, episodeNumber int, excludeID primitive.ObjectID) error {
filter := bson.M{
"mediaId": mediaID,
"episodeNumber": episodeNumber,
"isDelete": false,
}
if !excludeID.IsZero() {
filter["_id"] = bson.M{"$ne": excludeID}
}
count, err := mediacontentmod.QueryAllCount(filter)
if err != nil {
return err
}
if count > 0 {
return errors.New("剧集编号已存在")
}
return nil
}
func fileStatusToEpisodeStatus(status string) int {
switch status {
case vidmod.ConvertCompleted, vidmod.Completed:
return 1
case vidmod.FileUploadError, vidmod.MergeError, vidmod.ConvertError:
return 2
default:
return 0
}
}
func mediaMetadataFromFs(id string) (vidmod.AwsPullResp, int, error) {
resp, err := updownloadser.PullFileInfo(id)
if err != nil {
return vidmod.AwsPullResp{}, 0, err
}
status := fileStatusToEpisodeStatus(resp.Data.Status)
return resp.Data, status, nil
}
// Create 发布数据
func (p *WebCreateReq) Create(manager string) (err error, vid primitive.ObjectID) {
now := time.Now()
media, err := mediamod.QueryMediaByID(bson.M{"_id": p.MediaID})
if err != nil {
return err, primitive.ObjectID{}
}
if media == nil || media.ID.IsZero() {
return errors.New("media info is nll"), primitive.ObjectID{}
}
if media.IsDelete || p.MediaType != media.MediaType {
return errors.New("剧集mediaType与父媒体不一致"), primitive.ObjectID{}
}
p.Name = strings.TrimSpace(p.Name)
if p.MediaType == mediamod.MediaTypeVideo || p.MediaType == mediamod.MediaTypeDrama {
vid, err = primitive.ObjectIDFromHex(p.SourceID)
if err != nil || vid.IsZero() {
return errors.New("sourceID不是有效的资源ID"), primitive.ObjectID{}
}
} else {
vid = primitive.NewObjectID()
}
if err = ensureEpisodeNumberUnique(media.ID, int(p.EpisodeNumber), primitive.NilObjectID); err != nil {
return err, primitive.ObjectID{}
}
if media.MediaType == mediamod.MediaTypeDrama {
if err = validateDramaEpisodeState(int(p.EpisodeNumber), p.ListenPermission, p.Price, p.Name, p.VideoUrl, "", false, 0); err != nil {
return err, primitive.ObjectID{}
}
if strings.TrimSpace(p.VideoUrl) == "" {
return errors.New("短剧剧集必须提供videoUrl"), primitive.ObjectID{}
}
}
previewEnabled, previewStart, previewSeconds := false, 0, 0
if media.MediaType == mediamod.MediaTypeDrama {
previewEnabled, previewStart, previewSeconds, err = normalizeDramaPreview(p.PreviewEnabled, p.PreviewStart, p.PreviewSeconds, 0)
if err != nil {
return err, primitive.ObjectID{}
}
}
mc := mediacontentmod.MediaContent{
ID: vid,
MediaID: p.MediaID,
MediaType: media.MediaType,
Cover: p.Cover,
EpisodeNumber: int(p.EpisodeNumber),
ListenPermission: p.ListenPermission,
Price: p.Price,
Name: p.Name,
IsActive: p.IsActive,
IsDelete: false,
UpdatedAct: manager,
UpdateTime: now,
CreatedAt: now,
PreviewEnabled: previewEnabled,
PreviewStart: previewStart,
PreviewSeconds: previewSeconds,
}
// 视频和短剧都使用视频资源。
if media.MediaType == mediamod.MediaTypeVideo || media.MediaType == mediamod.MediaTypeDrama {
mc.Md5 = p.Md5
mc.VideoUrl = strings.TrimSpace(p.VideoUrl)
mc.Status = 0
if p.IsActive {
metadata, status, metadataErr := mediaMetadataFromFs(vid.Hex())
if metadataErr != nil {
return errors.New("文件服务连接失败,暂不能上架剧集"), primitive.ObjectID{}
}
mc.Status = status
mc.PlayTime = metadata.PlayTime
mc.Weight = metadata.Width
mc.Height = metadata.Height
mc.MediaSize = int64(metadata.Size)
if metadata.CheckSum != "" {
mc.Md5 = metadata.CheckSum
}
mc.Ratio = metadata.Ratio
if mc.Ratio == 0 && mc.Height > 0 {
mc.Ratio = float64(mc.Weight) / float64(mc.Height)
}
if media.MediaType == mediamod.MediaTypeDrama {
mc.PreviewEnabled, mc.PreviewStart, mc.PreviewSeconds, err = normalizeDramaPreview(mc.PreviewEnabled, mc.PreviewStart, mc.PreviewSeconds, mc.PlayTime)
if err != nil {
return err, primitive.ObjectID{}
}
if err = validateDramaEpisodeState(mc.EpisodeNumber, mc.ListenPermission, mc.Price, mc.Name, mc.VideoUrl, mc.H265Url, true, mc.Status); err != nil {
return err, primitive.ObjectID{}
}
}
}
}
// 图集
if media.MediaType == mediamod.MediaTypeImage {
mc.UrlSet = p.UrlSet
}
// 小说
if media.MediaType == mediamod.MediaTypeText {
mc.AudioUrl = p.AudioUrl
}
// 创建数据
if _, err := mediacontentmod.Insert(nil, mc); err != nil {
return err, primitive.ObjectID{}
}
err = OnMediaContentChange(media.ID, media.MediaType, true, mc.IsActive)
if err != nil {
return err, primitive.ObjectID{}
}
return nil, vid
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
EpisodeNumber *int `json:"episodeNumber"` // 第几集
ListenPermission *int `json:"listenPermission"` // 收听权限 0:会员 1:金币购买 2:免费
Price *int64 `json:"price"` // 购买价格
Name *string `json:"name"` // 章节名
Cover *string `json:"cover"` // 封面
Text *string `json:"text"` // 内容(小说专用)
Md5 *string `json:"md5"` // 媒体md5
VideoUrl *string `json:"videoUrl"` // 视频地址
H265Url *string `json:"h265Url"` // H.265视频地址
AudioUrl *string `json:"audioUrl"` // 有声小说地址
UrlSet *[]string `json:"urlSet"` // 地址集(多个资源地址用 例如漫画)
IsActive *bool `json:"isActive"` // 是否激活
PreviewEnabled *bool `json:"previewEnabled"` // 是否允许试看
PreviewStart *int `json:"previewStart"` // 试看起始时间(秒)
PreviewSeconds *int `json:"previewSeconds"` // 试看时长(秒)
}
// Update 更新数据
func (p *WebUpdateReq) Update(manager string) error {
mediaContent, err := mediacontentmod.GetInfo(p.ID)
if err != nil {
return err
}
if mediaContent.ID.IsZero() {
return errors.New("media content is null")
}
media, err := mediamod.GetInfo(mediaContent.MediaID)
if err != nil {
return err
}
next := mediaContent
if p.EpisodeNumber != nil {
next.EpisodeNumber = *p.EpisodeNumber
}
if p.ListenPermission != nil {
next.ListenPermission = *p.ListenPermission
}
if p.Price != nil {
next.Price = *p.Price
}
if p.Name != nil {
next.Name = strings.TrimSpace(*p.Name)
}
if p.VideoUrl != nil {
next.VideoUrl = strings.TrimSpace(*p.VideoUrl)
}
if p.H265Url != nil {
next.H265Url = strings.TrimSpace(*p.H265Url)
}
if p.IsActive != nil {
next.IsActive = *p.IsActive
}
if p.PreviewEnabled != nil {
next.PreviewEnabled = *p.PreviewEnabled
}
if p.PreviewStart != nil {
next.PreviewStart = *p.PreviewStart
}
if p.PreviewSeconds != nil {
next.PreviewSeconds = *p.PreviewSeconds
}
var metadata *vidmod.AwsPullResp
if next.IsActive && (media.MediaType == mediamod.MediaTypeVideo || media.MediaType == mediamod.MediaTypeDrama) && next.Status != 1 {
loaded, status, metadataErr := mediaMetadataFromFs(mediaContent.ID.Hex())
if metadataErr != nil {
return errors.New("文件服务连接失败,暂不能上架剧集")
}
metadata = &loaded
next.Status = status
next.PlayTime = loaded.PlayTime
next.Weight = loaded.Width
next.Height = loaded.Height
next.MediaSize = int64(loaded.Size)
next.Ratio = loaded.Ratio
if next.Ratio == 0 && next.Height > 0 {
next.Ratio = float64(next.Weight) / float64(next.Height)
}
}
if media.MediaType == mediamod.MediaTypeDrama {
next.PreviewEnabled, next.PreviewStart, next.PreviewSeconds, err = normalizeDramaPreview(next.PreviewEnabled, next.PreviewStart, next.PreviewSeconds, next.PlayTime)
if err != nil {
return err
}
if err = validateDramaEpisodeState(next.EpisodeNumber, next.ListenPermission, next.Price, next.Name, next.VideoUrl, next.H265Url, next.IsActive, next.Status); err != nil {
return err
}
if err = ensureEpisodeNumberUnique(media.ID, next.EpisodeNumber, mediaContent.ID); err != nil {
return err
}
}
data := make(map[string]interface{})
if p.EpisodeNumber != nil {
data["episodeNumber"] = *p.EpisodeNumber
}
if p.ListenPermission != nil {
data["listenPermission"] = *p.ListenPermission
}
if p.Price != nil {
data["price"] = *p.Price
}
if p.Name != nil {
data["name"] = next.Name
}
if p.Cover != nil {
data["cover"] = *p.Cover
}
if p.Text != nil {
data["text"] = *p.Text
}
if p.Md5 != nil {
data["md5"] = *p.Md5
}
if p.VideoUrl != nil {
data["videoUrl"] = next.VideoUrl
}
if p.H265Url != nil {
data["h265Url"] = next.H265Url
}
if p.AudioUrl != nil {
data["audioUrl"] = *p.AudioUrl
}
if p.UrlSet != nil {
data["urlSet"] = *p.UrlSet
}
if p.IsActive != nil {
data["isActive"] = *p.IsActive
}
if media.MediaType == mediamod.MediaTypeDrama && (p.PreviewEnabled != nil || p.PreviewStart != nil || p.PreviewSeconds != nil) {
data["previewEnabled"] = next.PreviewEnabled
data["previewStart"] = next.PreviewStart
data["previewSeconds"] = next.PreviewSeconds
}
if metadata != nil {
data["playTime"] = metadata.PlayTime
data["weight"] = metadata.Width
data["height"] = metadata.Height
data["mediaSize"] = metadata.Size
data["status"] = next.Status
data["ratio"] = next.Ratio
if metadata.CheckSum != "" {
data["md5"] = metadata.CheckSum
}
}
data["updatedAct"] = manager
data["updateTime"] = time.Now()
if _, err = mediacontentmod.UpdateByID(nil, p.ID, data); err != nil {
return err
}
published := p.IsActive != nil && *p.IsActive && !mediaContent.IsActive
err = OnMediaContentChange(media.ID, media.MediaType, false, published)
if err != nil {
return err
}
return nil
}
type WebBatchUpdateReq struct {
IDS []string `json:"ids" binding:"required"`
IsActive *bool `json:"isActive"` // 是否激活
Price *int64 `json:"price"` // 价格
Permission *int `json:"permission"` // 收听权限 0:会员 1:金币购买 2:免费
FreeEpisode *int `json:"freeEpisode"` // 前几集免费
}
func (p *WebBatchUpdateReq) Filter() []primitive.ObjectID {
ids, _ := p.ValidatedIDs()
return ids
}
func (p *WebBatchUpdateReq) ValidatedIDs() ([]primitive.ObjectID, error) {
if len(p.IDS) == 0 || len(p.IDS) > 100 {
return nil, errors.New("ids数量必须在1到100之间")
}
if p.IsActive == nil && p.Price == nil && p.Permission == nil && p.FreeEpisode == nil {
return nil, errors.New("至少需要一个批量更新字段")
}
if p.FreeEpisode != nil && *p.FreeEpisode < 0 {
return nil, errors.New("freeEpisode不能小于0")
}
ids := make([]primitive.ObjectID, 0, len(p.IDS))
seen := make(map[primitive.ObjectID]struct{}, len(p.IDS))
for _, raw := range p.IDS {
id, err := primitive.ObjectIDFromHex(raw)
if err != nil || id.IsZero() {
return nil, errors.New("ids包含无效的剧集ID")
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
ids = append(ids, id)
}
return ids, nil
}
func (p *WebBatchUpdateReq) applyContentFields(content mediacontentmod.MediaContent) mediacontentmod.MediaContent {
if p.IsActive != nil {
content.IsActive = *p.IsActive
}
if p.Price != nil {
content.Price = *p.Price
}
if p.Permission != nil {
content.ListenPermission = *p.Permission
}
return content
}
// Update 更新数据
func (p *WebBatchUpdateReq) Update(manager string) error {
ids, err := p.ValidatedIDs()
if err != nil {
return err
}
contents, err := mediacontentmod.QueryAllList(bson.M{
"_id": bson.M{"$in": ids},
"isDelete": false,
}, options.Find().SetLimit(int64(len(ids))))
if err != nil {
return err
}
if len(contents) != len(ids) {
return errors.New("部分剧集不存在或已删除")
}
mediaID := contents[0].MediaID
mediaType := contents[0].MediaType
published := false
for _, content := range contents {
if content.MediaID != mediaID {
return errors.New("批量操作的剧集必须属于同一部短剧")
}
next := p.applyContentFields(*content)
if p.IsActive != nil && *p.IsActive && !content.IsActive {
published = true
}
validateState := p.Price != nil || p.Permission != nil || (p.IsActive != nil && *p.IsActive)
if validateState && mediaType == mediamod.MediaTypeDrama {
if err = validateDramaEpisodeState(next.EpisodeNumber, next.ListenPermission, next.Price, next.Name, next.VideoUrl, next.H265Url, next.IsActive, next.Status); err != nil {
return fmt.Errorf("第%d集:%w", content.EpisodeNumber, err)
}
}
}
data := make(map[string]interface{})
if p.IsActive != nil {
data["isActive"] = *p.IsActive
}
if p.Price != nil {
data["price"] = *p.Price
}
if p.Permission != nil {
data["listenPermission"] = *p.Permission
}
data["updatedAct"] = manager
data["updateTime"] = time.Now()
contentChanged := p.IsActive != nil || p.Price != nil || p.Permission != nil
if contentChanged {
if _, err := mediacontentmod.UpdateByIDS(nil, ids, data); err != nil {
return err
}
if err = OnMediaContentChange(mediaID, mediaType, false, published); err != nil {
return err
}
}
if p.FreeEpisode != nil {
if _, err = mediamod.UpdateByID(nil, mediaID, map[string]interface{}{
"freeEpisode": *p.FreeEpisode,
"updatedAct": manager,
"updateTime": time.Now(),
}); err != nil {
return err
}
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
mediaContent, err := mediacontentmod.GetInfo(p.ID)
if err != nil {
return err
}
if err := mediacontentmod.DeleteByID(nil, p.ID); err != nil {
return err
}
err = OnMediaContentChange(mediaContent.MediaID, mediaContent.MediaType, false, false)
if err != nil {
return err
}
return nil
}
func OnMediaContentChange(mediaId primitive.ObjectID, mediaType string, contentAdded, published bool) (err error) {
// 获取当前最小的章节,重新update
defaultContent := mediamod.DefaultContent{}
if mediaType == mediamod.MediaTypeImage {
contentList, _, _, err := mediacontentmod.GetList(bson.M{"mediaId": mediaId, "isActive": true, "isDelete": false}, 0, 1, bson.D{{Key: "episodeNumber", Value: 1}, {Key: "createdAt", Value: 1}})
if err != nil {
return err
}
if len(contentList) > 0 {
defaultContent.Id = contentList[0].ID
defaultContent.Name = contentList[0].Name
defaultContent.UrlSet = contentList[0].UrlSet
defaultContent.Cover = contentList[0].Cover
}
}
// 子集总数
count, err := mediacontentmod.QueryAllCount(bson.M{"mediaId": mediaId, "isActive": true, "isDelete": false})
if err != nil {
return err
}
// 获取子集免费章节总数
freeCount, err := mediacontentmod.QueryAllCount(bson.M{"mediaId": mediaId, "listenPermission": 2, "isActive": true, "isDelete": false})
if err != nil {
return err
}
contentsPrice, err := mediacontentmod.QueryContentsPrice(mediaId)
if err != nil {
return
}
updateData := bson.M{
"currentEpisode": count,
"contentsPrice": contentsPrice,
"freeEpisode": freeCount,
"updateTime": time.Now(),
"defaultContent": defaultContent, // 默认展示的子集内容,冗余一个字段,减少一次查询
}
now := time.Now()
if contentAdded {
updateData["contentUpdateTime"] = now
}
if published {
updateData["latestPublishedAt"] = now
}
//修改当前父级的记录
_, err = mediamod.UpdateByID(nil, mediaId, updateData)
if err != nil {
return err
}
return nil
}
// ViewMediaFromFs 查看视频转码状态
func ViewMediaFromFs(id string) (code stderr.Code, data interface{}) {
resp, err := updownloadser.PullFileInfo(id)
if err != nil {
return stderr.ErrConnectToFs, nil
}
status := fileStatusToEpisodeStatus(resp.Data.Status)
ratio := resp.Data.Ratio
if ratio == 0 && resp.Data.Height > 0 {
ratio = float64(resp.Data.Width) / float64(resp.Data.Height)
}
set := map[string]interface{}{
"status": status,
"playTime": resp.Data.PlayTime,
"weight": resp.Data.Width,
"height": resp.Data.Height,
"mediaSize": int64(resp.Data.Size),
"ratio": ratio,
"updateTime": time.Now(),
}
if resp.Data.CheckSum != "" {
set["md5"] = resp.Data.CheckSum
}
objId, _ := primitive.ObjectIDFromHex(id)
if _, err = mediacontentmod.UpdateByID(nil, objId, set); err != nil {
return stderr.ErrDbUpdateError, nil
}
back := make(map[string]interface{}, 10)
back["playTime"] = resp.Data.PlayTime
back["size"] = resp.Data.Size
back["width"] = resp.Data.Width
back["height"] = resp.Data.Height
back["status"] = resp.Data.Status
back["ratio"] = ratio
fileName := resp.Data.Filename
fieldNameFs := resp.Data.FieldNameFs
back["fileName"] = filepath.Join(filepath.Dir(fieldNameFs), fileName)
switch resp.Data.Status {
case vidmod.UploadLoadingToFs:
back["statusDesc"] = "正在上传中"
case vidmod.UploadCompleted:
back["statusDesc"] = "上传完成"
case vidmod.FileUploadError:
back["statusDesc"] = "上传错误"
case vidmod.Merging:
back["statusDesc"] = "正在合并中"
case vidmod.MergeCompleted:
back["statusDesc"] = "合并完成"
case vidmod.MergeError:
back["statusDesc"] = "合并失败"
case vidmod.Converting:
back["statusDesc"] = "正在转码中"
case vidmod.ConvertError:
back["statusDesc"] = "转码失败"
case vidmod.ConvertCompleted, vidmod.Completed:
back["statusDesc"] = "完成状态"
}
return stderr.Success, back
}
+215
View File
@@ -0,0 +1,215 @@
package mediaresourceser
import (
"91porn-server/common"
"91porn-server/common/log"
mediaService "91porn-server/common/media"
"91porn-server/common/mediaCenter"
"91porn-server/web/webg"
"errors"
"go.mongodb.org/mongo-driver/bson/primitive"
"strings"
"sync"
)
type MediaListReq struct {
MediaType int `json:"media_type" form:"media_type"` // 1-动画 2-漫画 3-文本小说 4-有声小说 5-短剧
MediaCenterId *uint `json:"media_center_id" form:"media_center_id"` // 媒资中心的中资源唯一id
VloggerId *uint `json:"vlogger_id" form:"vlogger_id"` // up主id
IsUp *int `json:"is_up" form:"is_up"` // 是否是up主 0-不是 1-是 不传则是所有
Title *string `json:"title" form:"title"` // 标题
Tag *string `json:"tag" form:"tag"` // 标签
TerminalCdo *string `json:"terminal_cdo" form:"terminal_cdo"` // 上传者
SyncType int `json:"sync_type" form:"sync_type"` // 同步类型 1-全量同步 2-增量同步
Page uint `json:"page" form:"page"`
Size uint `json:"size" form:"size"`
}
type MediaListResp struct {
List []*mediaCenter.Media `json:"list"`
Total uint `json:"total"`
}
func (p *MediaListReq) Validate() error {
if p.MediaType < 1 || p.MediaType > 5 {
return errors.New("media_type只支持1到5")
}
if p.Page < 1 || p.Size < 1 || p.Size > 100 {
return errors.New("page必须大于0且size必须在1到100之间")
}
return nil
}
func (p *MediaListReq) GetList() (res MediaListResp, err error) {
ms := mediaCenter.NewMediaCenterService(mediaCenter.Config{
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
Secret: webg.Conf.MediaResourceDBCfg.Secret,
})
data, err := ms.MediaList(&mediaCenter.MediaListReq{
MediaType: p.MediaType,
Id: p.MediaCenterId,
VloggerId: p.VloggerId,
IsUp: p.IsUp,
Title: p.Title,
Tag: p.Tag,
TerminalCdo: p.TerminalCdo,
SyncType: p.SyncType,
Page: p.Page,
Size: p.Size,
})
if err != nil {
return
}
res.List = data.Data
res.Total = data.Pagination.Count
return
}
type MediaContentListReq struct {
MediaId int `json:"media_id" form:"media_id"`
Page uint `json:"page" form:"page"`
Size uint `json:"size" form:"size"`
}
type MediaContentListResp struct {
List []*mediaCenter.MediaContent `json:"list"`
Total uint `json:"total"`
}
func (p *MediaContentListReq) Validate() error {
if p.MediaId <= 0 {
return errors.New("media_id必须大于0")
}
if p.Page < 1 || p.Size < 1 || p.Size > 100 {
return errors.New("page必须大于0且size必须在1到100之间")
}
return nil
}
func (p *MediaContentListReq) GetList() (res MediaContentListResp, err error) {
ms := mediaCenter.NewMediaCenterService(mediaCenter.Config{
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
Secret: webg.Conf.MediaResourceDBCfg.Secret,
})
data, err := ms.MediaContentList(&mediaCenter.MediaContentListReq{
MediaId: p.MediaId,
Page: p.Page,
Size: p.Size,
})
if err != nil {
return
}
res.List = data.Data
res.Total = data.Pagination.Count
return
}
type MediaUseReq struct {
Ids []uint `json:"ids" form:"ids"`
Kind int `json:"kind" form:"kind" binding:"required"` //种类(1、哩番,2、3D,3、同人动画,4、单行本,5、连载漫画,6、韩漫 7、小说 8剧场
Type int `json:"type" form:"type" binding:"required"` //维度筛选 1-动漫 2-漫画
Dimension string `json:"dimension" form:"dimension" binding:"required"` //维度
SectionId primitive.ObjectID `json:"section_id" form:"section_id"` //专题id
}
func (p *MediaUseReq) Validate() error {
if len(p.Ids) == 0 || len(p.Ids) > 100 {
return errors.New("ids数量必须在1到100之间")
}
if strings.EqualFold(strings.TrimSpace(p.Dimension), "drama") {
if p.Kind != 8 || p.Type != 1 {
return errors.New("短剧导入时kind必须为8且type必须为1")
}
}
return nil
}
func (p *MediaUseReq) Use(admin string) (err error) {
if err = p.Validate(); err != nil {
return err
}
workerCount := 5
if len(p.Ids) < workerCount {
workerCount = len(p.Ids)
}
jobs := make(chan uint)
errs := make(chan error, len(p.Ids))
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
common.Go(func() {
defer wg.Done()
for id := range jobs {
if useErr := use(id, admin, p.Dimension, p.Type, p.Kind, p.SectionId); useErr != nil {
log.Error("MediaUse Fail", log.Any("id", id), log.E(useErr))
errs <- useErr
}
}
})
}
for _, id := range p.Ids {
jobs <- id
}
close(jobs)
wg.Wait()
close(errs)
for useErr := range errs {
if useErr != nil {
return useErr
}
}
return
}
func use(id uint, admin string, dimension string, dimensionType, kind int, sectionId primitive.ObjectID) (err error) {
mediaService := mediaService.NewMediaSync(webg.Redis, mediaCenter.NewMediaCenterService(mediaCenter.Config{
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
Secret: webg.Conf.MediaResourceDBCfg.Secret,
}))
// 调用use接口,在媒资库打上标记,避免还出现在增量数据里
err = mediaService.Ms.MediaUse(&mediaCenter.MediaUseReq{
Ids: []uint{id},
})
if err != nil {
return
}
// 因为需要同步的东西特别多,所以需要异步处理
mediaService.Worker.Exec(func() {
mediaService.UseMediaData(admin, id, dimension, dimensionType, kind, sectionId)
})
return
}
type MediaIgnoreReq struct {
Ids []uint `json:"ids" form:"ids"`
}
func (p *MediaIgnoreReq) Ignore() (err error) {
ms := mediaCenter.NewMediaCenterService(mediaCenter.Config{
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
Secret: webg.Conf.MediaResourceDBCfg.Secret,
})
// 在媒资库打上标记,避免还出现在增量数据里
return ms.MediaIgnore(&mediaCenter.MediaIgnoreReq{
Ids: p.Ids,
})
}
type MediaSyncReq struct {
}
// 执行同步的请求
func (p *MediaSyncReq) Sync(admin string) {
common.Go(func() {
mediaService := mediaService.NewMediaSync(webg.Redis, mediaCenter.NewMediaCenterService(mediaCenter.Config{
Url: webg.Conf.MediaResourceDBCfg.BaseUrl,
ProId: webg.Conf.MediaResourceDBCfg.Merchat,
Secret: webg.Conf.MediaResourceDBCfg.Secret,
}))
mediaService.SyncAll(admin)
})
}
@@ -0,0 +1,32 @@
package mediaresourceser
import "testing"
func TestMediaListReqValidate(t *testing.T) {
valid := MediaListReq{MediaType: 5, Page: 1, Size: 20}
if err := valid.Validate(); err != nil {
t.Fatalf("valid request rejected: %v", err)
}
invalidType := valid
invalidType.MediaType = 6
if err := invalidType.Validate(); err == nil {
t.Fatal("invalid media_type unexpectedly accepted")
}
invalidPage := valid
invalidPage.Page = 0
if err := invalidPage.Validate(); err == nil {
t.Fatal("page 0 unexpectedly accepted")
}
}
func TestMediaUseReqValidateDrama(t *testing.T) {
valid := MediaUseReq{Ids: []uint{1}, Dimension: "drama", Kind: 8, Type: 1}
if err := valid.Validate(); err != nil {
t.Fatalf("valid drama import rejected: %v", err)
}
invalid := valid
invalid.Kind = 1
if err := invalid.Validate(); err == nil {
t.Fatal("invalid drama kind unexpectedly accepted")
}
}
+106
View File
@@ -0,0 +1,106 @@
package mediaser
import (
"testing"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func validDramaCreateReq() WebCreateReq {
return WebCreateReq{
Title: "测试短剧",
HorizontalCover: "https://example.com/h.jpg",
VerticalCover: "https://example.com/v.jpg",
TotalEpisode: 10,
UpdateStatus: mediamod.Update,
MediaType: mediamod.MediaTypeDrama,
Kind: mediamod.Theater,
Permission: 1,
}
}
func TestWebCreateReqValidateDrama(t *testing.T) {
tests := []struct {
name string
mutate func(*WebCreateReq)
wantErr bool
}{
{name: "valid"},
{name: "missing cover", mutate: func(req *WebCreateReq) { req.VerticalCover = "" }, wantErr: true},
{name: "published before first episode", mutate: func(req *WebCreateReq) { req.Status = 1 }, wantErr: true},
{name: "wrong kind", mutate: func(req *WebCreateReq) { req.Kind = 1 }, wantErr: true},
{name: "wrong permission", mutate: func(req *WebCreateReq) { req.Permission = 2 }, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := validDramaCreateReq()
if tt.mutate != nil {
tt.mutate(&req)
}
if gotErr := req.Validate() != nil; gotErr != tt.wantErr {
t.Fatalf("Validate() error = %v, wantErr %v", gotErr, tt.wantErr)
}
})
}
}
func TestValidateDramaMediaForPublish(t *testing.T) {
media := mediamod.Media{
ID: primitive.NewObjectID(),
MediaType: mediamod.MediaTypeDrama,
Title: "测试短剧",
HorizontalCover: "h.jpg",
VerticalCover: "v.jpg",
TotalEpisode: 10,
UpdateStatus: mediamod.Update,
Kind: mediamod.Theater,
Permission: 1,
}
first := mediacontentmod.MediaContent{
ID: primitive.NewObjectID(),
MediaID: media.ID,
MediaType: mediamod.MediaTypeDrama,
EpisodeNumber: 1,
Name: "第1集",
Status: 1,
IsActive: true,
VideoUrl: "https://example.com/1.m3u8",
}
if err := validateDramaMediaForPublish(media, first); err != nil {
t.Fatalf("valid drama cannot publish: %v", err)
}
first.Status = 0
if err := validateDramaMediaForPublish(media, first); err == nil {
t.Fatal("transcoding first episode unexpectedly allowed to publish")
}
first.Status = 1
first.VideoUrl = ""
if err := validateDramaMediaForPublish(media, first); err == nil {
t.Fatal("first episode without play URL unexpectedly allowed to publish")
}
}
func TestWebListReqFilterUsesScalarValues(t *testing.T) {
status, kind, mediaCenterID := 1, mediamod.Theater, uint(123)
req := WebListReq{Status: &status, Kind: &kind, MediaCenterID: &mediaCenterID}
filter := req.Filter()
if filter["status"] != status || filter["kind"] != kind || filter["mediaCenterId"] != mediaCenterID {
t.Fatalf("unexpected filter: %#v", filter)
}
}
func TestWebBatchUpdateReqRejectsDramaParentPricing(t *testing.T) {
price := int64(30)
req := WebBatchUpdateReq{Price: &price}
medias := []*mediamod.Media{{MediaType: mediamod.MediaTypeDrama}}
if err := req.validateDramaBatchFields(medias); err == nil {
t.Fatal("drama parent pricing unexpectedly accepted")
}
req.Price = nil
if err := req.validateDramaBatchFields(medias); err != nil {
t.Fatalf("drama batch without parent pricing rejected: %v", err)
}
}
+1095
View File
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
package mediaser
import (
"fmt"
"strings"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/sensitivewordmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// CheckSensitive 单条媒资编辑的上架敏感词校验,规则对齐 vid/update:
//
// 1. status 被切到上架(1) —— 上架转换
// 2. 当前已上架(status==1) 且本次更新涉及 Title/Summary —— 已上架内容改动
//
// 校验文本以请求字段为准(提供则用新值,否则用 DB 现值)。
// 返回 (命中详情, true) 时调用方应拒绝本次更新;未命中或与上架无关返回 ("", false)。
func (p *WebUpdateReq) CheckSensitive() (string, bool) {
media, err := mediamod.GetInfo(p.ID)
if err != nil {
// 查不到交给后续 Update 流程报错,这里不拦
return "", false
}
// mediamod.Media.Status: 1 上架 / 0 下架
const statusUp = 1
shelvingTransition := p.Status != nil && *p.Status == statusUp
currentlyShelved := media.Status == statusUp
touchesText := p.Title != nil || p.Summary != nil
if !shelvingTransition && !(currentlyShelved && touchesText) {
return "", false
}
title := media.Title
summary := media.Summary
if p.Title != nil {
title = *p.Title
}
if p.Summary != nil {
summary = *p.Summary
}
terms := sensitivewordmod.LoadEnabledTerms()
if len(terms) == 0 {
return "", false
}
titleHits := sensitivewordmod.MatchHits(title, terms)
summaryHits := sensitivewordmod.MatchHits(summary, terms)
if len(titleHits) == 0 && len(summaryHits) == 0 {
return "", false
}
return formatMediaHitLine(p.ID, title, titleHits, summaryHits), true
}
// CheckSensitive 批量媒资更新的上架敏感词校验。
// 批量请求体不含文本字段,因此仅当 IsActive=true(批量上架)时,
// 对每个 id 的 DB 现值 Title/Summary 做命中检测。任一命中即拒绝,多条按行聚合。
func (p *WebBatchUpdateReq) CheckSensitive() (string, bool) {
if p.IsActive == nil || !*p.IsActive {
return "", false
}
terms := sensitivewordmod.LoadEnabledTerms()
if len(terms) == 0 {
return "", false
}
lines := make([]string, 0)
for _, id := range p.Filter() {
if id.IsZero() {
continue
}
media, err := mediamod.GetInfo(id)
if err != nil {
continue
}
titleHits := sensitivewordmod.MatchHits(media.Title, terms)
summaryHits := sensitivewordmod.MatchHits(media.Summary, terms)
if len(titleHits) == 0 && len(summaryHits) == 0 {
continue
}
lines = append(lines, formatMediaHitLine(media.ID, media.Title, titleHits, summaryHits))
}
if len(lines) == 0 {
return "", false
}
return strings.Join(lines, "\n"), true
}
// formatMediaHitLine 媒资命中描述:
// "id:xxxx 对象标题 XXXXXX。 标题命中 XX 简介命中 YY,不予上架成功"
func formatMediaHitLine(id primitive.ObjectID, title string, titleHits, summaryHits []string) string {
parts := make([]string, 0, 2)
if len(titleHits) > 0 {
parts = append(parts, "标题命中 "+strings.Join(titleHits, "、"))
}
if len(summaryHits) > 0 {
parts = append(parts, "简介命中 "+strings.Join(summaryHits, "、"))
}
return fmt.Sprintf("id:%s 对象标题 %s。 %s,不予上架成功", id.Hex(), title, strings.Join(parts, " "))
}
+201
View File
@@ -0,0 +1,201 @@
package mediatagdimensionser
import (
"91porn-server/common/log"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/mongo/options"
"91porn-server/models/cache/mediatagdimensiondata"
"91porn-server/models/v/mediatagdimensionmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
ID *string `form:"id" bson:"id,omitempty"` // ID
Name *string `form:"name" bson:"name,omitempty"` // 名称
Type *int `form:"type" bson:"type,omitempty"` // 类型 1:动漫,2:漫画
Status *int `form:"status" bson:"status,omitempty"` // 是否开启
PageNumber uint64 `form:"pageNumber" json:"pageNumber" binding:"required,min=1"` // 当前页
PageSize uint64 `form:"pageSize" json:"pageSize" binding:"required,min=1,max=100"` // 每页条数
}
type WebListRes struct {
Total int64 `json:"total"`
List []*mediatagdimensionmod.MediaTagDimension `json:"list"`
}
func (q *WebListReq) Filter() bson.M {
var query = bson.M{}
if q.Name != nil {
query["name"] = q.Name
}
if q.Status != nil {
query["status"] = q.Status
}
if q.Type != nil {
query["type"] = q.Type
}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
query["_id"] = id
}
query["isDelete"] = false
return query
}
func (q *WebListReq) Options() *options.FindOptions {
return options.Find().SetSkip(int64((q.PageNumber - 1) * q.PageSize)).SetLimit(int64(q.PageSize)).SetSort(bson.M{"sortCode": -1})
}
// GetList 获取列表
func (q *WebListReq) GetList() (res *WebListRes, err error) {
var out = &WebListRes{}
count, err := mediatagdimensionmod.QueryAllCount(q.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*mediatagdimensionmod.MediaTagDimension{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = mediatagdimensionmod.QueryAllList(q.Filter(), q.Options())
if err != nil {
log.Error(fmt.Sprintf("mediatagdimensionmod queryAllList error:%+v:", err))
return nil, err
}
return out, nil
}
type WebAllListReq struct {
Type *int `form:"type" bson:"type,omitempty"` // 类型 1:动漫,2:漫画
}
type WebAllListRes struct {
List []*mediatagdimensionmod.WebMediaTagDimension `json:"list"`
}
func (q *WebAllListReq) Filter() bson.M {
var query = bson.M{}
if q.Type != nil {
query["type"] = q.Type
}
query["status"] = 1
query["isDelete"] = false
return query
}
func (q *WebAllListReq) Options() *options.FindOptions {
return options.Find().SetSort(bson.M{"sortCode": -1})
}
// GetAllList 获取所有列表
func (q *WebAllListReq) GetAllList() (res *WebAllListRes, err error) {
var out = &WebAllListRes{}
data, err := mediatagdimensionmod.QueryAllList(q.Filter(), q.Options())
if err != nil {
log.Error(fmt.Sprintf("mediatagdimensionmod queryAllList error:%+v:", err))
return nil, err
}
if len(data) > 0 {
for _, d := range data {
mm := &mediatagdimensionmod.WebMediaTagDimension{
ID: d.ID,
Name: d.Name,
Type: d.Type,
}
out.List = append(out.List, mm)
}
}
return out, nil
}
type WebCreateReq struct {
Status int `json:"status" form:"status" binding:"required"` // 是否开启
Type int `json:"type" form:"type" binding:"required"` // 类型 1:动漫,2:漫画
Name string `json:"name" form:"name" binding:"required"` // 名称
SortCode int `json:"sortCode" form:"sortCode"` // 排序
}
// Create 发布数据
func (p *WebCreateReq) Create(manager string) error {
now := time.Now()
data := mediatagdimensionmod.MediaTagDimension{
SortCode: p.SortCode,
Status: p.Status,
Type: p.Type,
Name: p.Name,
IsDelete: false,
UpdatedAct: manager,
UpdateTime: now,
CreatedAt: now,
}
// 创建数据
if _, err := mediatagdimensiondata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
SortCode *int `json:"sortCode"` // 排序
Status *int `json:"status"` // 是否开启
Type *int `json:"type"` // 类型
Name *string `json:"name"` // 名称
}
// Update 更新数据
func (p *WebUpdateReq) Update(manager string) error {
coll, err := mediatagdimensionmod.GetInfo(p.ID)
if err != nil {
return err
}
if coll.ID.IsZero() {
return errors.New("media tag dimension is null")
}
data := make(map[string]interface{})
if p.SortCode != nil {
data["sortCode"] = *p.SortCode
}
if p.Status != nil {
data["status"] = *p.Status
}
if p.Type != nil {
data["type"] = *p.Type
}
if p.Name != nil {
data["name"] = *p.Name
}
data["updatedAct"] = manager
data["updateTime"] = time.Now()
if _, err = mediatagdimensiondata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := mediatagdimensiondata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
+221
View File
@@ -0,0 +1,221 @@
package mediatagser
import (
"91porn-server/common/log"
"fmt"
"time"
"go.mongodb.org/mongo-driver/mongo/options"
"91porn-server/models/cache/mediatagdata"
"91porn-server/models/commod"
"91porn-server/models/v/mediatagmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
ID *string `form:"id" bson:"id,omitempty"` // ID
Name *string `form:"name" bson:"name,omitempty"` // 名称
Type *int `form:"type" bson:"type,omitempty"` // 类型
Kind *int `form:"kind" bson:"kind,omitempty"` // 种类
Dimension *string `form:"dimension" bson:"dimension"` // 维度
Status *int `form:"status" bson:"status,omitempty"` // 是否开启
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
List []*mediatagmod.MediaTag `json:"list"`
}
func (q *WebListReq) Filter() bson.M {
var query = bson.M{}
if q.Name != nil {
query["name"] = q.Name
}
if q.Status != nil {
query["status"] = q.Status
}
if q.Kind != nil {
query["kind"] = q.Kind
}
if q.Type != nil {
query["type"] = q.Type
}
if q.Dimension != nil {
query["dimension"] = q.Dimension
}
if q.ID != nil {
id, _ := primitive.ObjectIDFromHex(*q.ID)
query["_id"] = id
}
query["isDelete"] = false
return query
}
func (q *WebListReq) Options() *options.FindOptions {
return options.Find().SetSkip(int64((q.PageNumber - 1) * q.PageSize)).SetLimit(int64(q.PageSize)).SetSort(bson.M{"sortCode": -1})
}
// GetList 获取列表
func (q *WebListReq) GetList() (res *WebListRes, err error) {
var out = &WebListRes{}
count, err := mediatagmod.QueryAllCount(q.Filter())
if err != nil {
return nil, err
}
if count == 0 {
out.List = []*mediatagmod.MediaTag{}
out.Total = 0
return out, nil
}
out.Total = count
out.List, err = mediatagmod.QueryAllList(q.Filter(), q.Options())
if err != nil {
log.Error(fmt.Sprintf("mediatagmod queryAllList error:%+v:", err))
return nil, err
}
return out, nil
}
type WebAllListReq struct {
Kind *int `form:"kind" bson:"kind,omitempty"` // 纬度分类 (1:动漫,2:漫画)
Dimension *string `form:"dimension" bson:"dimension"` // 维度名称
}
type WebAllListRes struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` // 文档id
Name string `json:"name" bson:"name"` // 标签名称
}
func (q *WebAllListReq) Filter() bson.M {
var query = bson.M{}
if q.Kind != nil {
query["kind"] = q.Kind
}
if q.Dimension != nil {
query["dimension"] = q.Dimension
}
query["isDelete"] = false
return query
}
func (q *WebAllListReq) Options() *options.FindOptions {
return options.Find().SetSort(bson.M{"sortCode": -1})
}
// GetALLList 获取列表
func (q *WebAllListReq) GetALLList() (res []*WebAllListRes, err error) {
var out []*WebAllListRes
data, err := mediatagmod.QueryAllList(q.Filter(), q.Options())
if err != nil {
log.Error(fmt.Sprintf("mediatagmod GetALLList error:%+v:", err))
return nil, err
}
if len(data) > 0 {
for _, d := range data {
out = append(out, &WebAllListRes{
ID: d.ID,
Name: d.Name,
})
}
}
return out, nil
}
type WebCreateReq struct {
Kind int `json:"kind" form:"kind" binding:"required"` // 种类(1:哩番,2:3D,3:同人动画,4:单行本,5:连载漫画,6:韩漫)
Type int `json:"type" form:"type" binding:"required"` // 类型(1:动漫,2:漫画 )
Dimension string `json:"dimension" form:"dimension" binding:"required"` // 维度
Name string `json:"name" form:"name" binding:"required"` // 标签名称
Active bool `json:"active" form:"active" binding:"required"` // 是都激活
IsDiscovery bool `json:"isDiscovery" bson:"isDiscovery"` // 是否推荐发现
Sort int `json:"sort" form:"sort"` // 排序码
}
// Create 发布数据
func (p *WebCreateReq) Create(manager string) error {
now := time.Now()
data := mediatagmod.MediaTag{
Kind: p.Kind,
Type: p.Type,
Dimension: p.Dimension,
Name: p.Name,
Active: p.Active,
Sort: p.Sort,
IsDiscovery: p.IsDiscovery,
IsDelete: false,
UpdatedAct: manager,
UpdateTime: now,
CreatedAt: now,
}
// 创建数据
if _, err := mediatagdata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"` // ID
Kind *int `json:"kind"` // 种类(1:哩番,2:3D,3:同人动画,4:单行本,5:连载漫画,6:韩漫)
Type *int `json:"type"` // 类型(1:动漫,2:漫画 )
Dimension *string `json:"dimension"` // 维度
Name *string `json:"name"` // 标签名称
Active *bool `json:"active"` // 是否激活
IsDiscovery *bool `json:"isDiscovery" bson:"isDiscovery"` // 是否推荐发现
Sort *int `json:"sort"` // 排序码
}
// Update 更新数据
func (p *WebUpdateReq) Update(manager string) error {
_, err := mediatagmod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Kind != nil {
data["kind"] = *p.Kind
}
if p.Type != nil {
data["type"] = *p.Type
}
if p.Dimension != nil {
data["dimension"] = *p.Dimension
}
if p.Name != nil {
data["name"] = *p.Name
}
if p.Active != nil {
data["active"] = *p.Active
}
if p.IsDiscovery != nil {
data["isDiscovery"] = *p.IsDiscovery
}
if p.Sort != nil {
data["sort"] = *p.Sort
}
data["updatedAct"] = manager
data["updateTime"] = time.Now()
if _, err = mediatagdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebDeleteReq struct {
ID primitive.ObjectID `json:"id" binding:"required"` // 文档ID
}
// Delete 删除数据
func (p *WebDeleteReq) Delete() error {
if err := mediatagdata.DeleteData(nil, p.ID); err != nil {
return err
}
return nil
}
@@ -0,0 +1,21 @@
package moduleser
import (
"testing"
"91porn-server/models/v/moduleconfmod"
)
func TestValidateDramaModuleName(t *testing.T) {
for _, name := range []string{"AI短剧", "热门短剧", "Drama Hot"} {
if err := validateDramaModuleName(moduleconfmod.Drama, name); err != nil {
t.Fatalf("valid module name %q rejected: %v", name, err)
}
}
if err := validateDramaModuleName(moduleconfmod.Drama, "短剧"); err == nil {
t.Fatal("ambiguous drama module name unexpectedly accepted")
}
if err := validateDramaModuleName(1, "任意名称"); err != nil {
t.Fatalf("non-drama module was affected: %v", err)
}
}
+355
View File
@@ -0,0 +1,355 @@
package moduleser
import (
"91porn-server/common/log"
"91porn-server/models/v/tagmod"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"strings"
"time"
sli "91porn-server/common/slice"
"91porn-server/common/stderr"
"91porn-server/models/commod"
"91porn-server/models/v/moduleconfmod"
"91porn-server/models/v/modulesectionmod"
"91porn-server/models/v/modulevidmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// AddModule 添加模块专题配置
func AddModule(mconf moduleconfmod.ModuleConf) error {
if err := validateDramaModuleName(mconf.Type, mconf.ModuleName); err != nil {
return err
}
return moduleconfmod.InsertOne(&mconf)
}
func validateDramaModuleName(moduleType int, moduleName string) error {
if moduleType != moduleconfmod.Drama {
return nil
}
name := strings.TrimSpace(moduleName)
lowerName := strings.ToLower(name)
if strings.Contains(lowerName, "ai") || strings.Contains(name, "热门") || strings.Contains(lowerName, "hot") {
return nil
}
return errors.New("短剧模块名称必须包含AI、热门或hot")
}
func AddSection(section modulesectionmod.Section) error {
return modulesectionmod.InsertOne(section)
}
// UpdateModule 更新模块专题配置
func UpdateModule(editSelector moduleconfmod.EditSelector) error {
current, err := moduleconfmod.GetByID(editSelector.ID)
if err != nil {
return err
}
moduleType := current.Type
moduleName := current.ModuleName
if editSelector.Type != nil {
moduleType = *editSelector.Type
}
if editSelector.ModuleName != nil {
moduleName = *editSelector.ModuleName
}
if err = validateDramaModuleName(moduleType, moduleName); err != nil {
return err
}
return moduleconfmod.UpdateOne(editSelector)
}
func UpdateSection(editSelector modulesectionmod.EditSelector) error {
var tagIds []primitive.ObjectID
if editSelector.Tags != nil && len(*editSelector.Tags) > 0 {
tags, err := tagmod.FindOneTagByNames(*editSelector.Tags)
if err != nil {
return err
}
if len(tags) <= 0 {
return errors.New("tag is null")
}
for _, t := range tags {
tagIds = append(tagIds, t.ID)
}
}
editSelector.TagIds = tagIds
return modulesectionmod.UpdateOne(editSelector)
}
func DeleteSection(id primitive.ObjectID) error {
return modulesectionmod.DeleteById(id)
}
// GetModuleList 获取专题下视频列表
func GetModuleList(query modulevidmod.QuerySelector, page commod.Page) (resp ModuleVideoList, err error) {
list, hasNext, total, err := modulevidmod.Search(&query, page)
if err != nil {
return
}
listLen := len(list)
sectionIDs := make([]primitive.ObjectID, listLen)
videoIDs := make([]primitive.ObjectID, listLen)
for i, sec := range list {
sectionIDs[i] = sec.SectionID
videoIDs[i] = sec.VideoID
}
sections, err := modulesectionmod.GetByIDs(sli.RemoveRepObjectID(sectionIDs))
if err != nil {
return
}
sectionModuleMap := make(map[primitive.ObjectID]modulesectionmod.SectionModule)
for _, v := range sections {
sectionModuleMap[v.ID] = v
}
videos, err := vidmod.GetVideoListByIDsNoStatus(videoIDs)
if err != nil {
return
}
videoMap := make(map[primitive.ObjectID]vidmod.VideoModel)
for _, video := range videos {
videoMap[video.ID] = *video
}
resp.List = make([]ModuleVideo, listLen)
for i, sec := range list {
secVideo := ModuleVideo{
ID: sec.ID,
SectionID: sec.SectionID,
CreatedAt: sec.CreatedAt,
SortCode: sec.SortCode,
}
if module, ok := sectionModuleMap[sec.SectionID]; ok {
secVideo.SectionName = module.SectionName
if len(module.SectionModule) > 0 {
secVideo.ModuleName = module.SectionModule[0].ModuleName
secVideo.SubModuleName = module.SectionModule[0].SubModuleName
}
}
secVideo.VideoInfo = VideoInfo{
VideoID: videoMap[sec.VideoID].ID,
Title: videoMap[sec.VideoID].Title,
PublisherID: videoMap[sec.VideoID].PublisherID,
Cover: videoMap[sec.VideoID].Cover,
}
resp.List[i] = secVideo
}
resp.Total = total
resp.HasNext = hasNext
return
}
// AddVideoBatch 专题下批量添加视频
func AddVideoBatch(req AddVideoBatchReq) error {
section, err := modulesectionmod.GetBySectionByID(req.SectionID)
if err != nil {
return stderr.ErrDbQueryError
}
if section.ID.IsZero() {
return stderr.CodeEmptyData
}
mId := section.SubModuleID
videoIdStrs := strings.Split(req.VideoIDs, ",")
vids := make([]primitive.ObjectID, len(videoIdStrs))
for i, vidIDStr := range videoIdStrs {
videoID, err := primitive.ObjectIDFromHex(vidIDStr)
if err != nil {
return stderr.ErrParamError
}
vids[i] = videoID
}
list, err := vidmod.GetVideoListByIDs(vids)
if err != nil {
return stderr.ErrNetWorkBusy
}
records := make([]modulevidmod.SectionVideo, len(list))
var vIds []primitive.ObjectID
var ids []primitive.ObjectID
for i := range list {
records[i] = modulevidmod.SectionVideo{
SectionID: req.SectionID,
VideoID: list[i].ID,
VideoReviewedAt: list[i].ReviewAt,
}
vIds = append(vIds, list[i].ID)
}
sectionVideos, err := modulevidmod.GetSectionsByVideoIds(vIds)
if err != nil {
return err
}
if len(sectionVideos) > 0 {
for i := range sectionVideos {
ids = append(ids, sectionVideos[i].ID)
}
}
if len(ids) > 0 {
err := modulevidmod.DeleteManyVideo(ids)
if err != nil {
return err
}
}
// 批量更新视频信息
update := bson.M{}
update["mId"] = mId.Hex()
update["updatedAt"] = time.Now()
result, err := vidmod.UpdateManyVideo(vIds, update)
if err != nil {
return err
}
if result == 0 {
return errors.New("video update failed")
}
return modulevidmod.InsertMany(records)
}
// AddVideo 专题下添加一条视频
func AddVideo(sectionVid modulevidmod.SectionVideo) error {
videoID := sectionVid.VideoID
video, err := vidmod.GetVideoInfo(videoID.Hex())
if err != nil {
return err
}
sectionVid.VideoReviewedAt = video.ReviewAt
sectionVid.NewsType = video.NewsType
return modulevidmod.InsertOne(&sectionVid)
}
// AllSections 获取所有专题
func AllSections() (data []modulesectionmod.ModuleSection, err error) {
modules, err := moduleconfmod.GetAllModule()
if err != nil {
return
}
sections, err := modulesectionmod.AllSections()
if err != nil {
return
}
sectionMap := make(map[primitive.ObjectID][]modulesectionmod.Section)
for _, s := range sections {
sectionMap[s.SubModuleID] = append(sectionMap[s.SubModuleID], s)
}
data = make([]modulesectionmod.ModuleSection, len(modules))
for i, m := range modules {
data[i] = modulesectionmod.ModuleSection{
ModuleConf: m,
Sections: sectionMap[m.ID],
}
}
return
}
func SectionSearch(q modulesectionmod.QuerySelector, p commod.Page) (resp modulesectionmod.ListResp, err error) {
sections, hasNext, total, err := modulesectionmod.Search(q, p)
if err != nil {
return
}
resp.HasNext = hasNext
resp.Total = total
sectionsLen := len(sections)
originalUserIDs := make([]uint64, 0, sectionsLen)
subModuleIDs := make([]primitive.ObjectID, 0, sectionsLen)
for _, section := range sections {
if section.OriginalUserID != nil && *section.OriginalUserID != 0 {
originalUserIDs = append(originalUserIDs, *section.OriginalUserID)
}
if !section.SubModuleID.IsZero() {
subModuleIDs = append(subModuleIDs, section.SubModuleID)
}
}
users, err := usermod.FindUsersByUID(originalUserIDs)
if err != nil {
return
}
subModules, err := moduleconfmod.FindByIDs(subModuleIDs)
if err != nil {
return
}
originalBloggerInfo := make(map[uint64]usermod.User)
subModuleInfo := make(map[primitive.ObjectID]moduleconfmod.ModuleConf)
for _, user := range users {
originalBloggerInfo[user.UID] = *user
}
for _, module := range subModules {
subModuleInfo[module.ID] = module
}
resp.List = make([]modulesectionmod.SectionDetail, sectionsLen)
for i, section := range sections {
detail := modulesectionmod.SectionDetail{
Section: section,
}
if section.OriginalUserID != nil && *section.OriginalUserID != 0 {
detail.OriginalBloggerInfo = modulesectionmod.OriginalBloggerInfo{
Name: originalBloggerInfo[*section.OriginalUserID].Name,
Portrait: originalBloggerInfo[*section.OriginalUserID].Portrait,
OfficialCert: originalBloggerInfo[*section.OriginalUserID].OfficialCert,
}
}
if !section.SubModuleID.IsZero() {
detail.ModuleInfo = modulesectionmod.ModuleInfo{
ModuleName: subModuleInfo[section.SubModuleID].ModuleName,
SubModuleName: subModuleInfo[section.SubModuleID].SubModuleName,
SectionLimit: subModuleInfo[detail.SubModuleID].SectionLimit,
}
}
resp.List[i] = detail
}
return
}
// QueryAllCartoonList 查询动漫模块专题配置
func QueryAllCartoonList() ([]*modulesectionmod.AllSectionConf, error) {
var out []*modulesectionmod.AllSectionConf
filter := bson.M{"type": bson.M{"$in": []int{moduleconfmod.Novel, moduleconfmod.Comics, moduleconfmod.Cartoon}}, "status": 1, "deletedAt": bson.M{"$exists": false}}
op := options.Find().SetSort(bson.M{"sortNum": -1})
list, err := moduleconfmod.QueryAllList(filter, op)
if err != nil {
log.Error(fmt.Sprintf("moduleconfmod QueryAllList error:%+v:", err))
return nil, err
}
var subIds []primitive.ObjectID
if len(list) > 0 {
for _, m := range list {
subIds = append(subIds, m.ID)
}
}
sf := bson.M{"subModuleID": bson.M{"$in": subIds}, "status": 1, "deletedAt": bson.M{"$exists": false}}
sop := options.Find().SetSort(bson.M{"sort": -1})
sections, err := modulesectionmod.QueryAllList(sf, sop)
if err != nil {
log.Error(fmt.Sprintf("modulesectionmod QueryAllList error:%+v:", err))
return nil, err
}
if len(sections) > 0 {
for _, m := range list {
mes := &modulesectionmod.AllSectionConf{
ID: m.ID,
ModuleName: m.ModuleName,
}
var allSection []modulesectionmod.SectionConf
for _, s := range sections {
if s.SubModuleID.Hex() == m.ID.Hex() {
nes := modulesectionmod.SectionConf{
ID: s.ID,
SectionName: s.SectionName,
}
allSection = append(allSection, nes)
}
}
mes.AllSection = allSection
out = append(out, mes)
}
}
return out, nil
}
+36
View File
@@ -0,0 +1,36 @@
package moduleser
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ModuleVideoList struct {
HasNext bool `json:"hasNext"` // 是否还有下一页
List []ModuleVideo `json:"list"` // 列表
Total int64 `json:"total"` // 总数
}
type ModuleVideo struct {
ID primitive.ObjectID `json:"id"` // ID
SectionID primitive.ObjectID `json:"sectionID"` // 专题id
SectionName string `json:"sectionName"` // 专题名称
ModuleName string `json:"moduleName"` // 模块名称
SubModuleName string `json:"subModuleName"` // 亚模块名称
SortCode int `json:"sortCode"` // 专题内排序
CreatedAt time.Time `json:"createdAt"` // 创建时间
VideoInfo // 视频信息
}
type VideoInfo struct {
VideoID primitive.ObjectID `json:"videoID"` // 视频ID
Title string `json:"title"` // 标题
PublisherID uint64 `json:"publisherID"` // 发布用户ID
Cover string `json:"cover"` // 视频封面图
}
type AddVideoBatchReq struct {
SectionID primitive.ObjectID `json:"sectionID" bson:"sectionID" binding:"required"` // 专题id
VideoIDs string `json:"videoIDs" binding:"required"` // 视频id列表,各个id以','分隔
}
+68
View File
@@ -0,0 +1,68 @@
package nakedchatorderser
import (
"time"
"91porn-server/models/cache/nakedchatorderdata"
"91porn-server/models/commod"
"91porn-server/models/v/nakedchatordermod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
Uid *uint64 `json:"uid" form:"uid"` // 用户id
OrderId *string `json:"orderId" form:"orderId"` // 订单id
Nid *string `json:"nid" bson:"nid"` // 裸聊id
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []nakedchatordermod.NakedChatOrder `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
if q.OrderId != nil {
orderId, _ := primitive.ObjectIDFromHex(*q.OrderId)
filter["_id"] = orderId
}
if q.Uid != nil {
filter["uid"] = *q.Uid
}
if q.Nid != nil {
nid, _ := primitive.ObjectIDFromHex(*q.Nid)
filter["nid"] = nid
}
res.List, res.Total, res.HasNext, err = nakedchatordermod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Status *int `json:"status"` // 0-已下单 1-已完成
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := nakedchatordermod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Status != nil {
data["status"] = *p.Status
}
data["updatedAt"] = time.Now()
if _, err = nakedchatorderdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
+201
View File
@@ -0,0 +1,201 @@
package nakedchatser
import (
"time"
"91porn-server/models/cache/nakedchatdata"
"91porn-server/models/commod"
"91porn-server/models/v/nakedchatmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type WebListReq struct {
Title *string `json:"title" form:"title"`
Status *int `json:"status" form:"status"` // 0-下架 1-上架
Mid *string `json:"mid" form:"mid"` // 模块id
commod.Page
}
type WebListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []nakedchatmod.NakedChat `json:"list"`
}
// GetList 获取列表
func (q *WebListReq) GetList() (res WebListRes, err error) {
filter := bson.M{}
if q.Title != nil {
filter["title"] = bson.M{
"$regex": *q.Title,
}
}
if q.Status != nil {
filter["status"] = *q.Status
}
if q.Mid != nil {
mid, _ := primitive.ObjectIDFromHex(*q.Mid)
filter["mid"] = mid
}
res.List, res.Total, res.HasNext, err = nakedchatmod.GetList(filter, int64(q.Skip()), int64(q.Limit()), q.GetSort())
return
}
type WebCreateReq struct {
Title string `json:"title" form:"title" binding:"required"` // 标题
Cover string `json:"cover" form:"cover" binding:"required"` // 封面
Price uint64 `json:"price" form:"price" binding:"required"` // 价格 n金币/分钟
Options []uint64 `json:"options" form:"options" binding:"required"` // 可供购买选项 ,多少分钟
Images []string `json:"images" form:"images"` // 图片列表
Video string `json:"video" form:"video"` // 展示的视频
Contact string `json:"contact" form:"contact" binding:"required"` // 联系方式
Age int `json:"age" form:"age" binding:"required"` // 年龄 单位 岁
Weight int `json:"weight" form:"weight" binding:"required"` // 体重 单位 kg
Height int `json:"height" form:"height" binding:"required"` // 身高 单位 cm
Cup string `json:"cup" form:"cup" binding:"required"` // 罩杯
FakeSaleNum int64 `json:"fakeSaleNum" form:"fakeSaleNum" binding:"required"` // 销售数量(假)
BusinessHours string `json:"businessHours" form:"businessHours" binding:"required"` // 连线时间
Summary string `json:"summary" form:"summary"` // 简介
Status int `json:"status" form:"status"` // 0-下架 1-上架
SortCode int64 `json:"sortCode" bson:"sortCode"` // 排序号
}
// Create 发布数据
func (p *WebCreateReq) Create() error {
data := nakedchatmod.NakedChat{
Title: p.Title,
Cover: p.Cover,
Price: p.Price,
Options: p.Options,
Images: p.Images,
Video: p.Video,
Contact: p.Contact,
Age: p.Age,
Weight: p.Weight,
Height: p.Height,
Cup: p.Cup,
FakeSaleNum: p.FakeSaleNum,
BusinessHours: p.BusinessHours,
Summary: p.Summary,
Status: p.Status,
SortCode: p.SortCode,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// 创建数据
if _, err := nakedchatdata.InsertData(nil, data); err != nil {
return err
}
return nil
}
type WebUpdateReq struct {
ID primitive.ObjectID `json:"id" binding:"required"`
Title *string `json:"title"` // 标题
Cover *string `json:"cover"` // 封面
Price *int64 `json:"price"` // 价格 n金币/分钟
Options *[]int64 `json:"options"` // 可供购买选项 ,多少分钟
Images *[]string `json:"images"` // 图片列表
Video *string `json:"video"` // 展示的视频
Contact *string `json:"contact"` // 联系方式
Age *int `json:"age"` // 年龄 单位 岁
Weight *int `json:"weight"` // 体重 单位 kg
Height *int `json:"height"` // 身高 单位 cm
Cup *string `json:"cup"` // 罩杯
FakeSaleNum *int64 `json:"fakeSaleNum"` // 销售数量(假)
BusinessHours *string `json:"businessHours"` // 连线时间
Summary *string `json:"summary"` // 简介
Status *int `json:"status" bson:"status"` // 0-下架 1-上架
SortCode *int64 `json:"sortCode" bson:"sortCode"` // 排序号
}
// Update 更新数据
func (p *WebUpdateReq) Update() error {
_, err := nakedchatmod.GetInfo(p.ID)
if err != nil {
return err
}
data := make(map[string]interface{})
if p.Title != nil {
data["title"] = *p.Title
}
if p.Cover != nil {
data["cover"] = *p.Cover
}
if p.Price != nil {
data["price"] = *p.Price
}
if p.Options != nil {
data["options"] = *p.Options
}
if p.Images != nil {
data["images"] = *p.Images
}
if p.Video != nil {
data["video"] = *p.Video
}
if p.Contact != nil {
data["contact"] = *p.Contact
}
if p.Age != nil {
data["age"] = *p.Age
}
if p.Weight != nil {
data["weight"] = *p.Weight
}
if p.Height != nil {
data["height"] = *p.Height
}
if p.Cup != nil {
data["cup"] = *p.Cup
}
if p.FakeSaleNum != nil {
data["fakeSaleNum"] = *p.FakeSaleNum
}
if p.BusinessHours != nil {
data["businessHours"] = *p.BusinessHours
}
if p.Summary != nil {
data["summary"] = *p.Summary
}
if p.Status != nil {
data["status"] = *p.Status
}
if p.SortCode != nil {
data["sortCode"] = *p.SortCode
}
data["updatedAt"] = time.Now()
if _, err = nakedchatdata.UpdateData(nil, p.ID.Hex(), data); err != nil {
return err
}
return nil
}
type WebBatchUpdateReq struct {
Ids []primitive.ObjectID `json:"ids" binding:"required"`
Mid *primitive.ObjectID `json:"mid" form:"mid"` // 模块id
}
// Update 更新数据
func (p *WebBatchUpdateReq) Update() error {
data := make(map[string]interface{})
if p.Mid != nil {
data["mid"] = *p.Mid
}
data["updatedAt"] = time.Now()
for _, id := range p.Ids {
if _, err := nakedchatdata.UpdateData(nil, id.Hex(), data); err != nil {
return err
}
}
return nil
}
+284
View File
@@ -0,0 +1,284 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListAlbum(req *AlbumListReq) (int64, []officialwebsitemod.Album, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Album{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveAlbumSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveAlbumSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, title, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Album{}).Count(filter)
return n > 0, err
})
}
func CreateAlbum(req *ModifyAlbumReq) (officialwebsitemod.Album, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Album{}, err
}
if data.SeoSlug, err = resolveAlbumSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID); err != nil {
return officialwebsitemod.Album{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Album{}, err
}
return data, nil
}
func UpdateAlbum(req *ModifyAlbumReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Album{}
err = mod.FindOne(officialwebsitemod.M{"_id": id})
if err != nil {
return
}
if isNilObjectID(mod.ID) {
err = stderr.ErrParamError
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveAlbumSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteAlbum(req *DeleteAlbumReq) (data DeleteAlbumResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Album{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyAlbumReq struct {
ID string `json:"id" form:"id"`
HeroID *string `json:"heroID" form:"heroID"`
Title *string `json:"title"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
IsHot *bool `json:"isHot"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteAlbumReq struct {
ID string `json:"id" form:"id"`
}
type DeleteAlbumResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyAlbumReq → model struct
// ─────────────────────────────────────────
func (req *ModifyAlbumReq) toMod() (officialwebsitemod.Album, error) {
if req == nil {
return officialwebsitemod.Album{}, nil
}
data := officialwebsitemod.Album{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return officialwebsitemod.Album{}, stderr.ErrParamError
}
data.HeroID = heroID
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data, nil
}
// ─────────────────────────────────────────
// toM — ModifyAlbumReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyAlbumReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroID"] = heroID
}
}
if req.Title != nil {
set["title"] = *req.Title
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type AlbumListReq struct {
PageReq
ID *string `form:"id"`
HeroID *string `form:"heroID"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
IsHot *bool `form:"isHot"`
Tags *string `form:"tags"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *AlbumListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if err := parseObjectIDFilter(filter, "heroID", r.HeroID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+141
View File
@@ -0,0 +1,141 @@
package officialwebsiteser
import (
"91porn-server/common/log"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"github.com/robfig/cron/v3"
)
var seoSlugBackfillCron *cron.Cron
// StartSeoSlugBackfillCron 启动定时任务:扫描官网各表(Video/Hero/Album/News/Photograph)
// 中缺失 seoSlug 的记录,按标题自动生成并回填。
// 启动后异步先跑一次(补存量),之后每小时扫一次(兜底导入/批量遗漏的记录)。
func StartSeoSlugBackfillCron() {
go backfillAllSeoSlug() // 首次异步执行,避免阻塞服务启动
seoSlugBackfillCron = cron.New()
if _, err := seoSlugBackfillCron.AddFunc("@every 1h", backfillAllSeoSlug); err != nil {
log.Error("StartSeoSlugBackfillCron AddFunc fail", log.E(err))
return
}
seoSlugBackfillCron.Start()
}
func backfillAllSeoSlug() {
backfillVideoSeoSlug()
backfillHeroSeoSlug()
backfillAlbumSeoSlug()
backfillNewsSeoSlug()
backfillPhotographSeoSlug()
}
// seoSlugMissingFilter 匹配 seoSlug 字段缺失或为空的记录。
func seoSlugMissingFilter() officialwebsitemod.M {
return officialwebsitemod.M{"$or": []officialwebsitemod.M{
{"seoSlug": officialwebsitemod.M{"$exists": false}},
{"seoSlug": ""},
}}
}
// setSeoSlug 回填单条记录的 seoSlug。
func setSeoSlug(update func(officialwebsitemod.M, officialwebsitemod.M) (int64, error), id interface{}, slug string) {
if _, err := update(officialwebsitemod.M{"_id": id}, officialwebsitemod.M{"$set": officialwebsitemod.M{"seoSlug": slug}}); err != nil {
log.Error("backfill seoSlug update fail", log.E(err), log.Any("id", id))
}
}
func backfillVideoSeoSlug() {
mod := &officialwebsitemod.Video{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill video seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveVideoSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillHeroSeoSlug() {
mod := &officialwebsitemod.Hero{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill hero seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Name == "" {
continue
}
slug, e := resolveHeroSeoSlug("", list[i].Name, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillAlbumSeoSlug() {
mod := &officialwebsitemod.Album{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill album seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveAlbumSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillNewsSeoSlug() {
mod := &officialwebsitemod.News{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill news seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Title == "" {
continue
}
slug, e := resolveNewsSeoSlug("", list[i].Title, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
func backfillPhotographSeoSlug() {
mod := &officialwebsitemod.Photograph{}
list, err := mod.FindMany(seoSlugMissingFilter())
if err != nil {
log.Error("backfill photograph seoSlug find fail", log.E(err))
return
}
for i := range list {
if list[i].Name == "" {
continue
}
slug, e := resolvePhotographSeoSlug("", list[i].Name, list[i].ID)
if e != nil || slug == "" {
continue
}
setSeoSlug(mod.Update, list[i].ID, slug)
}
}
+779
View File
@@ -0,0 +1,779 @@
package officialwebsiteser
import (
"91porn-server/common"
"91porn-server/common/cachev2"
"91porn-server/common/constant/redisconst"
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func GetBasicData() (data GetBasicDataResp, err error) {
mod := &officialwebsitemod.BasicData{}
if err = mod.FindOne(); err != nil {
return
}
err = data.transfer(mod)
return
}
func CreateBasicData(req *ModifyBasicDataReq) (data officialwebsitemod.BasicData, err error) {
data, err = req.toMod()
if err != nil {
return
}
if err = data.Create(); err != nil {
return officialwebsitemod.BasicData{}, err
}
return
}
func UpdateBasicData(req *ModifyBasicDataReq) (resp UpdateBasicDataResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BasicData{}
if err = mod.FindOne(); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
common.Go(func() {
// 删除缓存
cachev2.Classes().
CacheTime(redisconst.OfficialWebsiteBasicDataCacheExpire).
AutoListKey(redisconst.OfficialWebsiteBasicDataCacheKey).
DeleteCurrent()
})
return
}
func DeleteBasicData(req *DeleteBasicDataReq) (data DeleteBasicDataResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BasicData{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Query (BasicData) types
// ─────────────────────────────────────────
type GetBasicDataReq struct{}
type GetBasicDataResp struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Title string `json:"title" bson:"title"`
Description string `json:"description" bson:"description"`
Banner []GBDBanner `json:"banner" bson:"banner"`
HeroBanner []GBDHeroBanner `json:"heroBanner" bson:"heroBanner"`
Masterpiece []GBDMasterpiece `json:"masterpiece" bson:"masterpiece"`
Business []GBDBusiness `json:"business" bson:"business"`
BannersDescription string `json:"bannersDescription" bson:"bannersDescription"`
BannersDuration int `json:"bannersDuration" bson:"bannersDuration"`
AboutUs GBDAboutUs `json:"aboutUs" bson:"aboutUs"`
FAQ []GBDFAQ `json:"faq" bson:"faq"`
HomePageCMS []GBDCMSData `json:"homePageCMS" bson:"homePageCMS"`
HomePageCMSDescription string `json:"homePageCMSDescription" bson:"homePageCMSDescription"`
RecruitCMS []GBDCMSData `json:"recruitCMS" bson:"recruitCMS"`
Tags []officialwebsitemod.Tag `json:"tags" bson:"tags"`
}
type GBDBanner struct {
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Image string `json:"image" bson:"image"`
PhoneImage string `json:"phoneImage" bson:"phoneImage"`
Thumbnail string `json:"thumbnail" bson:"thumbnail"`
URL string `json:"url" bson:"url"`
}
type GBDHeroBanner struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
HeroID string `json:"heroId" bson:"heroId"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Image string `json:"image" bson:"image"`
URL string `json:"url" bson:"url"`
Duration int `json:"duration" bson:"duration"`
}
type GBDMasterpiece struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Description string `json:"description" bson:"description"`
Cover string `json:"cover" bson:"cover"`
Type string `json:"type" bson:"type"`
}
type GBDBusiness struct {
Name string `json:"name" bson:"name"`
EnglishName string `json:"englishName" bson:"englishName"`
Description string `json:"description" bson:"description"`
Cover string `json:"cover" bson:"cover"`
Detail []GBDBusinessDetail `json:"detail" bson:"detail"`
}
type GBDBusinessDetail struct {
Cover string `json:"cover" bson:"cover"`
Description string `json:"description" bson:"description"`
}
type GBDAboutUs struct {
Description string `json:"description" bson:"description"`
Companies []GBDCompanyInfo `json:"companies" bson:"companies"`
Values string `json:"values" bson:"values"`
ValuesImg string `json:"valuesImg" bson:"valuesImg"`
}
type GBDCompanyInfo struct {
Name string `json:"name" bson:"name"`
Address string `json:"address" bson:"address"`
Description string `json:"description" bson:"description"`
Contact []GBDContactInfo `json:"contact" bson:"contact"`
}
type GBDContactInfo struct {
Type string `json:"type" bson:"type"`
Value string `json:"value" bson:"value"`
}
type GBDFAQ struct {
Question string `json:"question" bson:"question"`
Answer string `json:"answer" bson:"answer"`
}
type GBDCMSData struct {
Description string `json:"description" bson:"description"`
Value string `json:"value" bson:"value"`
}
func (data *GetBasicDataResp) transfer(mod *officialwebsitemod.BasicData) error {
if mod == nil {
return stderr.CodeEmptyData
}
data.ID = mod.ID
data.Title = mod.Title
data.Description = mod.Description
data.Tags = mod.Tags
data.BannersDescription = mod.BannersDescription
data.BannersDuration = mod.BannersDuration
data.HomePageCMSDescription = mod.HomePageCMSDescription
data.AboutUs.Description = mod.AboutUs.Description
data.AboutUs.Values = mod.AboutUs.Values
data.AboutUs.ValuesImg = mod.AboutUs.ValuesImg
data.Banner = make([]GBDBanner, 0, len(mod.Banner))
for _, v := range mod.Banner {
data.Banner = append(data.Banner, GBDBanner{
Name: v.Name, Description: v.Description, Image: v.Image, PhoneImage: v.PhoneImage, Thumbnail: v.Thumbnail, URL: v.URL,
})
}
data.HeroBanner = make([]GBDHeroBanner, 0, len(mod.HeroBanner))
for _, v := range mod.HeroBanner {
data.HeroBanner = append(data.HeroBanner, GBDHeroBanner{
ID: v.ID, HeroID: v.HeroID.Hex(), Name: v.Name, Description: v.Description, Image: v.Image, URL: v.URL, Duration: v.Duration,
})
}
data.Masterpiece = make([]GBDMasterpiece, 0, len(mod.Masterpiece))
for _, v := range mod.Masterpiece {
data.Masterpiece = append(data.Masterpiece, GBDMasterpiece{
ID: v.ID, Name: v.Name, Description: v.Description, Cover: v.Cover, Type: v.Type,
})
}
data.Business = make([]GBDBusiness, 0, len(mod.Business))
for _, v := range mod.Business {
business := GBDBusiness{
Name: v.Name, EnglishName: v.EnglishName, Description: v.Description, Cover: v.Cover,
Detail: make([]GBDBusinessDetail, 0, len(v.Detail)),
}
for _, d := range v.Detail {
business.Detail = append(business.Detail, GBDBusinessDetail{
Cover: d.Cover, Description: d.Description,
})
}
data.Business = append(data.Business, business)
}
data.AboutUs.Companies = make([]GBDCompanyInfo, 0, len(mod.AboutUs.Companies))
for _, v := range mod.AboutUs.Companies {
company := GBDCompanyInfo{
Name: v.Name, Address: v.Address, Description: v.Description,
Contact: make([]GBDContactInfo, 0, len(v.Contact)),
}
for _, c := range v.Contact {
company.Contact = append(company.Contact, GBDContactInfo{Type: c.Type, Value: c.Value})
}
data.AboutUs.Companies = append(data.AboutUs.Companies, company)
}
data.FAQ = make([]GBDFAQ, 0, len(mod.FAQ))
for _, v := range mod.FAQ {
data.FAQ = append(data.FAQ, GBDFAQ{Question: v.Question, Answer: v.Answer})
}
data.HomePageCMS = make([]GBDCMSData, 0, len(mod.HomePageCMS))
for _, v := range mod.HomePageCMS {
data.HomePageCMS = append(data.HomePageCMS, GBDCMSData{Description: v.Description, Value: v.Value})
}
data.RecruitCMS = make([]GBDCMSData, 0, len(mod.RecruitCMS))
for _, v := range mod.RecruitCMS {
data.RecruitCMS = append(data.RecruitCMS, GBDCMSData{Description: v.Description, Value: v.Value})
}
return nil
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyBasicDataReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title" bson:"title"`
Description *string `json:"description" bson:"description"`
Banner *[]ModifyBanner `json:"banner" bson:"banner"`
HeroBanner *[]ModifyHeroBanner `json:"heroBanner" bson:"heroBanner"`
Masterpiece *[]ModifyMasterpiece `json:"masterpiece" bson:"masterpiece"`
Business *[]ModifyBusiness `json:"business" bson:"business"`
BannersDescription *string `json:"bannersDescription" bson:"bannersDescription"`
BannersDuration *int `json:"bannersDuration" bson:"bannersDuration"`
AboutUs *ModifyAboutUs `json:"aboutUs" bson:"aboutUs"`
FAQ *[]ModifyFAQ `json:"faq" bson:"faq"`
HomePageCMS *[]ModifyCMSData `json:"homePageCMS" bson:"homePageCMS"`
HomePageCMSDescription *string `json:"homePageCMSDescription" bson:"homePageCMSDescription"`
RecruitCMS *[]ModifyCMSData `json:"recruitCMS" bson:"recruitCMS"`
Tags *[]officialwebsitemod.Tag `json:"tags" bson:"tags"`
}
type ModifyBanner struct {
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Image *string `json:"image" bson:"image"`
PhoneImage *string `json:"phoneImage" bson:"phoneImage"`
Thumbnail *string `json:"thumbnail" bson:"thumbnail"`
URL *string `json:"url" bson:"url"`
}
type ModifyHeroBanner struct {
ID *primitive.ObjectID `json:"id" bson:"_id,omitempty"`
HeroID *primitive.ObjectID `json:"heroId" bson:"heroId"`
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Image *string `json:"image" bson:"image"`
URL *string `json:"url" bson:"url"`
Duration *int `json:"duration" bson:"duration"`
}
type ModifyMasterpiece struct {
ID *primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name *string `json:"name" bson:"name"`
Description *string `json:"description" bson:"description"`
Cover *string `json:"cover" bson:"cover"`
Type *string `json:"type" bson:"type"`
}
type ModifyBusiness struct {
Name *string `json:"name" bson:"name"`
EnglishName *string `json:"englishName" bson:"englishName"`
Description *string `json:"description" bson:"description"`
Cover *string `json:"cover" bson:"cover"`
Detail *[]ModifyBusinessDetail `json:"detail" bson:"detail"`
}
type ModifyBusinessDetail struct {
Cover *string `json:"cover" bson:"cover"`
Description *string `json:"description" bson:"description"`
}
type ModifyAboutUs struct {
Description *string `json:"description" bson:"description"`
Companies *[]ModifyCompanyInfo `json:"companies" bson:"companies"`
Values *string `json:"values" bson:"values"`
ValuesImg *string `json:"valuesImg" bson:"valuesImg"`
}
type ModifyCompanyInfo struct {
Name *string `json:"name" bson:"name"`
Address *string `json:"address" bson:"address"`
Description *string `json:"description" bson:"description"`
Contact *[]ModifyContactInfo `json:"contact" bson:"contact"`
}
type ModifyContactInfo struct {
Type *string `json:"type" bson:"type"`
Value *string `json:"value" bson:"value"`
}
type ModifyFAQ struct {
Question *string `json:"question" bson:"question"`
Answer *string `json:"answer" bson:"answer"`
}
type ModifyCMSData struct {
Description *string `json:"description" bson:"description"`
Value *string `json:"value" bson:"value"`
}
type UpdateBasicDataResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteBasicDataReq struct {
ID string `json:"id"`
}
type DeleteBasicDataResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — Modify* → model struct
// ─────────────────────────────────────────
func (v ModifyBanner) toMod() officialwebsitemod.Banner {
return officialwebsitemod.Banner{
Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Image: stringOrZero(v.Image), Thumbnail: stringOrZero(v.Thumbnail),
PhoneImage: stringOrZero(v.PhoneImage), URL: stringOrZero(v.URL),
}
}
func (v ModifyHeroBanner) toMod() officialwebsitemod.HeroBanner {
return officialwebsitemod.HeroBanner{
HeroID: objectIDOrZero(v.HeroID), Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Image: stringOrZero(v.Image), URL: stringOrZero(v.URL), Duration: intOrZero(v.Duration),
}
}
func (v ModifyMasterpiece) toMod() officialwebsitemod.Masterpiece {
return officialwebsitemod.Masterpiece{
ID: objectIDOrZero(v.ID), Name: stringOrZero(v.Name), Description: stringOrZero(v.Description),
Cover: stringOrZero(v.Cover), Type: stringOrZero(v.Type),
}
}
func (v ModifyBusiness) toMod() officialwebsitemod.Business {
out := officialwebsitemod.Business{
Name: stringOrZero(v.Name), EnglishName: stringOrZero(v.EnglishName),
Description: stringOrZero(v.Description), Cover: stringOrZero(v.Cover),
}
if v.Detail != nil {
out.Detail = make([]officialwebsitemod.BusinessDetail, 0, len(*v.Detail))
for _, d := range *v.Detail {
out.Detail = append(out.Detail, d.toMod())
}
}
return out
}
func (v ModifyBusinessDetail) toMod() officialwebsitemod.BusinessDetail {
return officialwebsitemod.BusinessDetail{
Cover: stringOrZero(v.Cover), Description: stringOrZero(v.Description),
}
}
func (v ModifyContactInfo) toMod() officialwebsitemod.ContactInfo {
return officialwebsitemod.ContactInfo{Type: stringOrZero(v.Type), Value: stringOrZero(v.Value)}
}
func (v ModifyCompanyInfo) toMod() officialwebsitemod.CompanyInfo {
out := officialwebsitemod.CompanyInfo{
Name: stringOrZero(v.Name), Address: stringOrZero(v.Address), Description: stringOrZero(v.Description),
}
if v.Contact != nil {
out.Contact = make([]officialwebsitemod.ContactInfo, 0, len(*v.Contact))
for _, c := range *v.Contact {
out.Contact = append(out.Contact, c.toMod())
}
}
return out
}
func (v *ModifyAboutUs) toMod() officialwebsitemod.AboutUs {
if v == nil {
return officialwebsitemod.AboutUs{}
}
out := officialwebsitemod.AboutUs{
Description: stringOrZero(v.Description), Values: stringOrZero(v.Values), ValuesImg: stringOrZero(v.ValuesImg),
}
if v.Companies != nil {
out.Companies = make([]officialwebsitemod.CompanyInfo, 0, len(*v.Companies))
for _, c := range *v.Companies {
out.Companies = append(out.Companies, c.toMod())
}
}
return out
}
func (v ModifyFAQ) toMod() officialwebsitemod.FAQ {
return officialwebsitemod.FAQ{Question: stringOrZero(v.Question), Answer: stringOrZero(v.Answer)}
}
func (v ModifyCMSData) toMod() officialwebsitemod.CMSData {
return officialwebsitemod.CMSData{Description: stringOrZero(v.Description), Value: stringOrZero(v.Value)}
}
// toMod 将请求结构体转换为完整的 BasicData model
func (req *ModifyBasicDataReq) toMod() (officialwebsitemod.BasicData, error) {
if req == nil {
return officialwebsitemod.BasicData{}, stderr.CodeEmptyData
}
data := officialwebsitemod.BasicData{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
BannersDescription: stringOrZero(req.BannersDescription),
BannersDuration: intOrZero(req.BannersDuration),
HomePageCMSDescription: stringOrZero(req.HomePageCMSDescription),
}
if req.AboutUs != nil {
data.AboutUs = req.AboutUs.toMod()
}
if req.Banner != nil {
data.Banner = make([]officialwebsitemod.Banner, 0, len(*req.Banner))
for _, b := range *req.Banner {
data.Banner = append(data.Banner, b.toMod())
}
}
if req.HeroBanner != nil {
data.HeroBanner = make([]officialwebsitemod.HeroBanner, 0, len(*req.HeroBanner))
for _, h := range *req.HeroBanner {
data.HeroBanner = append(data.HeroBanner, h.toMod())
}
}
if req.Masterpiece != nil {
data.Masterpiece = make([]officialwebsitemod.Masterpiece, 0, len(*req.Masterpiece))
for _, m := range *req.Masterpiece {
data.Masterpiece = append(data.Masterpiece, m.toMod())
}
}
if req.Business != nil {
data.Business = make([]officialwebsitemod.Business, 0, len(*req.Business))
for _, b := range *req.Business {
data.Business = append(data.Business, b.toMod())
}
}
if req.FAQ != nil {
data.FAQ = make([]officialwebsitemod.FAQ, 0, len(*req.FAQ))
for _, f := range *req.FAQ {
data.FAQ = append(data.FAQ, f.toMod())
}
}
if req.HomePageCMS != nil {
data.HomePageCMS = make([]officialwebsitemod.CMSData, 0, len(*req.HomePageCMS))
for _, h := range *req.HomePageCMS {
data.HomePageCMS = append(data.HomePageCMS, h.toMod())
}
}
if req.RecruitCMS != nil {
data.RecruitCMS = make([]officialwebsitemod.CMSData, 0, len(*req.RecruitCMS))
for _, r := range *req.RecruitCMS {
data.RecruitCMS = append(data.RecruitCMS, r.toMod())
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
return data, nil
}
// ─────────────────────────────────────────
// toM — Modify* → officialwebsitemod.M (Mongo update doc)
// ─────────────────────────────────────────
func (v ModifyBanner) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Image != nil {
out["image"] = *v.Image
}
if v.Thumbnail != nil {
out["thumbnail"] = *v.Thumbnail
}
if v.PhoneImage != nil {
out["phoneImage"] = *v.PhoneImage
}
if v.URL != nil {
out["url"] = *v.URL
}
return out
}
func (v ModifyHeroBanner) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.ID != nil {
out["_id"] = *v.ID
}
if v.HeroID != nil {
out["heroId"] = *v.HeroID
}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Image != nil {
out["image"] = *v.Image
}
if v.URL != nil {
out["url"] = *v.URL
}
if v.Duration != nil {
out["duration"] = *v.Duration
}
return out
}
func (v ModifyMasterpiece) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.ID != nil {
out["_id"] = *v.ID
}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Type != nil {
out["type"] = *v.Type
}
return out
}
func (v ModifyBusiness) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.EnglishName != nil {
out["englishName"] = *v.EnglishName
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Detail != nil {
detail := make([]officialwebsitemod.M, 0, len(*v.Detail))
for _, d := range *v.Detail {
detail = append(detail, d.toM())
}
out["detail"] = detail
}
return out
}
func (v ModifyBusinessDetail) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Cover != nil {
out["cover"] = *v.Cover
}
if v.Description != nil {
out["description"] = *v.Description
}
return out
}
func (v ModifyContactInfo) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Type != nil {
out["type"] = *v.Type
}
if v.Value != nil {
out["value"] = *v.Value
}
return out
}
func (v ModifyCompanyInfo) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Name != nil {
out["name"] = *v.Name
}
if v.Address != nil {
out["address"] = *v.Address
}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Contact != nil {
contacts := make([]officialwebsitemod.M, 0, len(*v.Contact))
for _, c := range *v.Contact {
contacts = append(contacts, c.toM())
}
out["contact"] = contacts
}
return out
}
func (v *ModifyAboutUs) toM() officialwebsitemod.M {
if v == nil {
return nil
}
out := officialwebsitemod.M{}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Values != nil {
out["values"] = *v.Values
}
if v.ValuesImg != nil {
out["valuesImg"] = *v.ValuesImg
}
if v.Companies != nil {
companies := make([]officialwebsitemod.M, 0, len(*v.Companies))
for _, c := range *v.Companies {
companies = append(companies, c.toM())
}
out["companies"] = companies
}
return out
}
func (v ModifyFAQ) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Question != nil {
out["question"] = *v.Question
}
if v.Answer != nil {
out["answer"] = *v.Answer
}
return out
}
func (v ModifyCMSData) toM() officialwebsitemod.M {
out := officialwebsitemod.M{}
if v.Description != nil {
out["description"] = *v.Description
}
if v.Value != nil {
out["value"] = *v.Value
}
return out
}
// toM 将请求中的非 nil 字段构造为 Mongo $set 更新文档
func (req *ModifyBasicDataReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.BannersDescription != nil {
set["bannersDescription"] = *req.BannersDescription
}
if req.BannersDuration != nil {
set["bannersDuration"] = *req.BannersDuration
}
if req.HomePageCMSDescription != nil {
set["homePageCMSDescription"] = *req.HomePageCMSDescription
}
if req.Banner != nil {
banners := make([]officialwebsitemod.M, 0, len(*req.Banner))
for _, b := range *req.Banner {
banners = append(banners, b.toM())
}
set["banner"] = banners
}
if req.HeroBanner != nil {
heroBanners := make([]officialwebsitemod.M, 0, len(*req.HeroBanner))
for _, h := range *req.HeroBanner {
heroBanners = append(heroBanners, h.toM())
}
set["heroBanner"] = heroBanners
}
if req.Masterpiece != nil {
masterpiece := make([]officialwebsitemod.M, 0, len(*req.Masterpiece))
for _, m := range *req.Masterpiece {
masterpiece = append(masterpiece, m.toM())
}
set["masterpiece"] = masterpiece
}
if req.Business != nil {
businesses := make([]officialwebsitemod.M, 0, len(*req.Business))
for _, b := range *req.Business {
businesses = append(businesses, b.toM())
}
set["business"] = businesses
}
if req.AboutUs != nil {
aboutUsM := req.AboutUs.toM()
if len(aboutUsM) > 0 {
set["aboutUs"] = aboutUsM
}
}
if req.FAQ != nil {
faqs := make([]officialwebsitemod.M, 0, len(*req.FAQ))
for _, f := range *req.FAQ {
faqs = append(faqs, f.toM())
}
set["faq"] = faqs
}
if req.HomePageCMS != nil {
cms := make([]officialwebsitemod.M, 0, len(*req.HomePageCMS))
for _, h := range *req.HomePageCMS {
cms = append(cms, h.toM())
}
set["homePageCMS"] = cms
}
if req.RecruitCMS != nil {
rcms := make([]officialwebsitemod.M, 0, len(*req.RecruitCMS))
for _, r := range *req.RecruitCMS {
rcms = append(rcms, r.toM())
}
set["recruitCMS"] = rcms
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
+209
View File
@@ -0,0 +1,209 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListBusiness(req *BusinessListReq) (int64, []officialwebsitemod.BusinessData, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.BusinessData{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateBusiness(req *ModifyBusinessReq) (officialwebsitemod.BusinessData, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.BusinessData{}, err
}
return data, nil
}
func UpdateBusiness(req *ModifyBusinessReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BusinessData{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteBusiness(req *DeleteBusinessReq) (data DeleteBusinessResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.BusinessData{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyBusinessReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
Description *string `json:"description"`
Type *string `json:"type"`
ServiceOverview *string `json:"serviceOverview"`
CollaborationProcess *string `json:"collaborationProcess"`
CaseStudies *string `json:"caseStudies"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteBusinessReq struct {
ID string `json:"id" form:"id"`
}
type DeleteBusinessResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyBusinessReq → model struct
// ─────────────────────────────────────────
func (req *ModifyBusinessReq) toMod() officialwebsitemod.BusinessData {
if req == nil {
return officialwebsitemod.BusinessData{}
}
return officialwebsitemod.BusinessData{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Type: stringOrZero(req.Type),
ServiceOverview: stringOrZero(req.ServiceOverview),
CollaborationProcess: stringOrZero(req.CollaborationProcess),
CaseStudies: stringOrZero(req.CaseStudies),
}
}
// ─────────────────────────────────────────
// toM — ModifyBusinessReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyBusinessReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Type != nil {
set["type"] = *req.Type
}
if req.ServiceOverview != nil {
set["serviceOverview"] = *req.ServiceOverview
}
if req.CollaborationProcess != nil {
set["collaborationProcess"] = *req.CollaborationProcess
}
if req.CaseStudies != nil {
set["caseStudies"] = *req.CaseStudies
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type BusinessListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Type *string `form:"type"`
ServiceOverview *string `form:"serviceOverview"`
CollaborationProcess *string `form:"collaborationProcess"`
CaseStudies *string `form:"caseStudies"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *BusinessListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Type != nil {
filter["type"] = *r.Type
}
if r.ServiceOverview != nil {
filter["serviceOverview"] = *r.ServiceOverview
}
if r.CollaborationProcess != nil {
filter["collaborationProcess"] = *r.CollaborationProcess
}
if r.CaseStudies != nil {
filter["caseStudies"] = *r.CaseStudies
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+286
View File
@@ -0,0 +1,286 @@
package officialwebsiteser
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
)
// ─────────────────────────────────────────
// List Helper Types and Functions
// ─────────────────────────────────────────
// CountResp 通用操作返回
type CountResp struct {
Count int64 `json:"count"`
}
// PageReq 分页请求
type PageReq struct {
Page int64 `form:"page"`
PageSize int64 `form:"pageSize"`
}
func (r *PageReq) FindOptions() *options.FindOptions {
page := r.Page
if page <= 0 {
page = 1
}
pageSize := r.PageSize
if pageSize <= 0 {
pageSize = 20
}
skip := (page - 1) * pageSize
return options.Find().SetSkip(skip).SetLimit(pageSize).SetSort(officialwebsitemod.M{"createdAt": -1})
}
func parseObjectIDFilter(filter officialwebsitemod.M, key string, raw *string) error {
if raw == nil || *raw == "" {
return nil
}
id, err := primitive.ObjectIDFromHex(*raw)
if err != nil {
return fmt.Errorf("invalid %s: %w", key, err)
}
filter[key] = id
return nil
}
func parseJSONFilter(filter officialwebsitemod.M, key string, raw *string, out interface{}) error {
if raw == nil || *raw == "" {
return nil
}
if err := json.Unmarshal([]byte(*raw), out); err != nil {
return fmt.Errorf("invalid %s json: %w", key, err)
}
v := reflect.ValueOf(out)
if v.Kind() == reflect.Ptr {
filter[key] = v.Elem().Interface()
return nil
}
filter[key] = out
return nil
}
// ────────────────────────────────────────
// Update Helper Functions
// ─────────────────────────────────────────
func modelToMap(doc interface{}) (officialwebsitemod.M, error) {
raw, err := bson.Marshal(doc)
if err != nil {
return nil, err
}
data := officialwebsitemod.M{}
if err := bson.Unmarshal(raw, &data); err != nil {
return nil, err
}
delete(data, "_id")
delete(data, "createdAt")
delete(data, "updatedAt")
delete(data, "deletedAt")
return data, nil
}
func modelChanged(oldDoc interface{}, newDoc interface{}) (bool, error) {
oldMap, err := modelToMap(oldDoc)
if err != nil {
return false, err
}
newMap, err := modelToMap(newDoc)
if err != nil {
return false, err
}
return !reflect.DeepEqual(oldMap, newMap), nil
}
func buildModelUpdateDoc(doc interface{}) (officialwebsitemod.M, error) {
data, err := modelToMap(doc)
if err != nil {
return nil, err
}
data["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": data}, nil
}
func buildPointerUpdateDoc(oldDoc interface{}, updateDoc interface{}) (officialwebsitemod.M, error) {
oldVal, err := structValueOf(oldDoc, "old doc")
if err != nil {
return nil, err
}
newVal, err := structValueOf(updateDoc, "update doc")
if err != nil {
return nil, err
}
set := officialwebsitemod.M{}
collectPointerChanges(set, oldVal, newVal)
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
func structValueOf(doc interface{}, label string) (reflect.Value, error) {
val := reflect.ValueOf(doc)
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return reflect.Value{}, errors.New(label + " is nil")
}
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return reflect.Value{}, errors.New(label + " must be struct")
}
return val, nil
}
func collectPointerChanges(set officialwebsitemod.M, oldVal reflect.Value, newVal reflect.Value) {
newType := newVal.Type()
for i := 0; i < newVal.NumField(); i++ {
addPointerFieldChange(set, oldVal, newVal.Field(i), newType.Field(i))
}
}
func addPointerFieldChange(set officialwebsitemod.M, oldVal reflect.Value, newField reflect.Value, fieldInfo reflect.StructField) {
if fieldInfo.Name == "ID" {
return
}
if !newField.IsValid() || newField.Kind() != reflect.Ptr || newField.IsNil() {
return
}
oldField := findStructFieldByName(oldVal, fieldInfo.Name)
if !oldField.IsValid() {
return
}
newValue := derefValue(newField)
oldValue := derefValue(oldField)
if !newValue.IsValid() || !oldValue.IsValid() {
return
}
if reflect.DeepEqual(oldValue.Interface(), newValue.Interface()) {
return
}
key := bsonFieldName(fieldInfo)
if key == "" {
return
}
set[key] = newValue.Interface()
}
func findStructFieldByName(val reflect.Value, name string) reflect.Value {
if !val.IsValid() {
return reflect.Value{}
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return reflect.Value{}
}
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return reflect.Value{}
}
if field := val.FieldByName(name); field.IsValid() {
return field
}
valType := val.Type()
for i := 0; i < val.NumField(); i++ {
fieldType := valType.Field(i)
if !fieldType.Anonymous {
continue
}
field := findStructFieldByName(val.Field(i), name)
if field.IsValid() {
return field
}
}
return reflect.Value{}
}
func derefValue(v reflect.Value) reflect.Value {
for v.IsValid() && v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
return v
}
func bsonFieldName(field reflect.StructField) string {
if tag := field.Tag.Get("bson"); tag != "" {
name := strings.Split(tag, ",")[0]
if name != "" && name != "-" {
return name
}
}
if tag := field.Tag.Get("json"); tag != "" {
name := strings.Split(tag, ",")[0]
if name != "" && name != "-" {
return name
}
}
return field.Name
}
// ─────────────────────────────────────────
// Pointer helpers
// ─────────────────────────────────────────
func stringOrZero(v *string) string {
if v == nil {
return ""
}
return *v
}
func intOrZero(v *int) int {
if v == nil {
return 0
}
return *v
}
func objectIDOrZero(v *primitive.ObjectID) primitive.ObjectID {
if v == nil {
return primitive.NilObjectID
}
return *v
}
func int64OrZero(v *int64) int64 {
if v == nil {
return 0
}
return *v
}
func boolOrZero(v *bool) bool {
if v == nil {
return false
}
return *v
}
func isNilObjectID(id primitive.ObjectID) bool {
return id == primitive.NilObjectID
}
+300
View File
@@ -0,0 +1,300 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListHero(req *HeroListReq) (int64, []officialwebsitemod.Hero, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Hero{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateHero(req *ModifyHeroReq) (officialwebsitemod.Hero, error) {
data := req.toMod()
// SEO slug:手填优先(校验唯一),为空则由名称生成雏形并去重
var err error
if data.SeoSlug, err = resolveHeroSeoSlug(data.SeoSlug, data.Name, primitive.NilObjectID); err != nil {
return officialwebsitemod.Hero{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Hero{}, err
}
return data, nil
}
// resolveHeroSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由名称生成雏形并追加后缀去重。
func resolveHeroSeoSlug(provided, name string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, name, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Hero{}).Count(filter)
return n > 0, err
})
}
func UpdateHero(req *ModifyHeroReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Hero{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveHeroSeoSlug(*req.SeoSlug, mod.Name, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteHero(req *DeleteHeroReq) (data DeleteHeroResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Hero{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyHeroReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
SeoSlug *string `json:"seoSlug"`
Avatar *string `json:"avatar"`
Description *string `json:"description"`
FansCount *int64 `json:"fansCount"`
Height *int64 `json:"height"`
Bust *int64 `json:"bust"`
Waist *int64 `json:"waist"`
Hip *int64 `json:"hip"`
Cover *string `json:"cover"`
Partners *[]primitive.ObjectID `json:"partners"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteHeroReq struct {
ID string `json:"id" form:"id"`
}
type DeleteHeroResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyHeroReq → model struct
// ─────────────────────────────────────────
func (req *ModifyHeroReq) toMod() officialwebsitemod.Hero {
if req == nil {
return officialwebsitemod.Hero{}
}
data := officialwebsitemod.Hero{
Name: stringOrZero(req.Name),
Avatar: stringOrZero(req.Avatar),
Description: stringOrZero(req.Description),
FansCount: int64OrZero(req.FansCount),
Height: int64OrZero(req.Height),
Bust: int64OrZero(req.Bust),
Waist: int64OrZero(req.Waist),
Hip: int64OrZero(req.Hip),
Cover: stringOrZero(req.Cover),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.Partners != nil {
data.Partners = *req.Partners
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyHeroReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyHeroReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Avatar != nil {
set["avatar"] = *req.Avatar
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.FansCount != nil {
set["fansCount"] = *req.FansCount
}
if req.Height != nil {
set["height"] = *req.Height
}
if req.Bust != nil {
set["bust"] = *req.Bust
}
if req.Waist != nil {
set["waist"] = *req.Waist
}
if req.Hip != nil {
set["hip"] = *req.Hip
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Partners != nil {
set["partners"] = *req.Partners
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type HeroListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Avatar *string `form:"avatar"`
Height *int64 `form:"height"`
Bust *int64 `form:"bust"`
Waist *int64 `form:"waist"`
Hip *int64 `form:"hip"`
Description *string `form:"description"`
FansCount *int64 `form:"fansCount"`
Cover *string `form:"cover"`
Partners *string `form:"partners"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *HeroListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Avatar != nil {
filter["avatar"] = *r.Avatar
}
if r.Height != nil {
filter["height"] = *r.Height
}
if r.Bust != nil {
filter["bust"] = *r.Bust
}
if r.Waist != nil {
filter["waist"] = *r.Waist
}
if r.Hip != nil {
filter["hip"] = *r.Hip
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.FansCount != nil {
filter["fansCount"] = *r.FansCount
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if err := parseJSONFilter(filter, "partners", r.Partners, &[]primitive.ObjectID{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+220
View File
@@ -0,0 +1,220 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListJobList(req *JobListReq) (int64, []officialwebsitemod.Job, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Job{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateJobList(req *ModifyJobReq) (officialwebsitemod.Job, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Job{}, err
}
return data, nil
}
func UpdateJobList(req *ModifyJobReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Job{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteJobList(req *DeleteJobReq) (data DeleteJobResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Job{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyJobReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
Description *string `json:"description"`
Requirements *string `json:"requirements"`
Benefits *string `json:"benefits"`
JDUrl *string `json:"jdUrl"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteJobReq struct {
ID string `json:"id" form:"id"`
}
type DeleteJobResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyJobReq → model struct
// ─────────────────────────────────────────
func (req *ModifyJobReq) toMod() officialwebsitemod.Job {
if req == nil {
return officialwebsitemod.Job{}
}
return officialwebsitemod.Job{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Requirements: stringOrZero(req.Requirements),
Benefits: stringOrZero(req.Benefits),
JDUrl: stringOrZero(req.JDUrl),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyJobReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyJobReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Requirements != nil {
set["requirements"] = *req.Requirements
}
if req.Benefits != nil {
set["benefits"] = *req.Benefits
}
if req.JDUrl != nil {
set["jdUrl"] = *req.JDUrl
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type JobListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Requirements *string `form:"requirements"`
Benefits *string `form:"benefits"`
JDUrl *string `form:"jdUrl"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *JobListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Requirements != nil {
filter["requirements"] = *r.Requirements
}
if r.Benefits != nil {
filter["benefits"] = *r.Benefits
}
if r.JDUrl != nil {
filter["jdUrl"] = *r.JDUrl
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+261
View File
@@ -0,0 +1,261 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListNews(req *NewsListReq) (int64, []officialwebsitemod.News, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.News{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveNewsSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveNewsSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, title, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.News{}).Count(filter)
return n > 0, err
})
}
func CreateNews(req *ModifyNewsReq) (officialwebsitemod.News, error) {
data := req.toMod()
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
slug, err := resolveNewsSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID)
if err != nil {
return officialwebsitemod.News{}, err
}
data.SeoSlug = slug
if err := data.Create(); err != nil {
return officialwebsitemod.News{}, err
}
return data, nil
}
func UpdateNews(req *ModifyNewsReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.News{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveNewsSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteNews(req *DeleteNewsReq) (data DeleteNewsResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.News{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyNewsReq struct {
ID string `json:"id" form:"id"`
Title *string `json:"title"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Url *string `json:"url"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Detail *string `json:"detail"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteNewsReq struct {
ID string `json:"id" form:"id"`
}
type DeleteNewsResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyNewsReq → model struct
// ─────────────────────────────────────────
func (req *ModifyNewsReq) toMod() officialwebsitemod.News {
if req == nil {
return officialwebsitemod.News{}
}
data := officialwebsitemod.News{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
Url: stringOrZero(req.Url),
Detail: stringOrZero(req.Detail),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyNewsReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyNewsReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Title != nil {
set["title"] = *req.Title
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Detail != nil {
set["detail"] = *req.Detail
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type NewsListReq struct {
PageReq
ID *string `form:"id"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Url *string `form:"url"`
Tags *string `form:"tags"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *NewsListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.Url != nil {
filter["url"] = *r.Url
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+211
View File
@@ -0,0 +1,211 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListPartner(req *PartnerListReq) (int64, []officialwebsitemod.Partner, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Partner{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreatePartner(req *ModifyPartnerReq) (officialwebsitemod.Partner, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Partner{}, err
}
return data, nil
}
func UpdatePartner(req *ModifyPartnerReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Partner{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeletePartner(req *DeletePartnerReq) (data DeletePartnerResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Partner{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyPartnerReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Description *string `json:"description"`
Logo *string `json:"logo"`
Url *string `json:"url"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeletePartnerReq struct {
ID string `json:"id" form:"id"`
}
type DeletePartnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyPartnerReq → model struct
// ─────────────────────────────────────────
func (req *ModifyPartnerReq) toMod() officialwebsitemod.Partner {
if req == nil {
return officialwebsitemod.Partner{}
}
return officialwebsitemod.Partner{
Name: stringOrZero(req.Name),
Description: stringOrZero(req.Description),
Logo: stringOrZero(req.Logo),
Url: stringOrZero(req.Url),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyPartnerReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyPartnerReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Logo != nil {
set["logo"] = *req.Logo
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type PartnerListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Description *string `form:"description"`
Logo *string `form:"logo"`
Url *string `form:"url"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *PartnerListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Logo != nil {
filter["logo"] = *r.Logo
}
if r.Url != nil {
filter["url"] = *r.Url
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
@@ -0,0 +1,331 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListPhotograph(req *ListPhotographReq) (int64, []officialwebsitemod.Photograph, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Photograph{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolvePhotographSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolvePhotographSeoSlug(provided, name string, excludeID primitive.ObjectID) (string, error) {
return ResolveSeoSlug(provided, name, func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Photograph{}).Count(filter)
return n > 0, err
})
}
func CreatePhotograph(req *ModifyPhotographReq) (officialwebsitemod.Photograph, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Photograph{}, err
}
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
if data.SeoSlug, err = resolvePhotographSeoSlug(data.SeoSlug, data.Name, primitive.NilObjectID); err != nil {
return officialwebsitemod.Photograph{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Photograph{}, err
}
return data, nil
}
func UpdatePhotograph(req *ModifyPhotographReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Photograph{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
if isNilObjectID(mod.ID) {
err = stderr.ErrParamError
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolvePhotographSeoSlug(*req.SeoSlug, mod.Name, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeletePhotograph(req *DeletePhotographReq) (data DeletePhotographResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Photograph{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
func BatchUpdatePhotographOwner(req *BatchUpdatePhotographOwnerReq) (data BatchUpdatePhotographOwnerResp, err error) {
if req == nil {
err = stderr.ErrParamError
return
}
// Check owner is exist
var ownerID primitive.ObjectID
ownerID, err = primitive.ObjectIDFromHex(req.OwnerID)
if err != nil {
return
}
var heroMod = &officialwebsitemod.Hero{}
err = heroMod.FindOne(officialwebsitemod.M{"_id": ownerID})
if err != nil {
return
}
if isNilObjectID(heroMod.ID) {
err = stderr.OfficialWebsiteHeroExist
return
}
var photographIDs []primitive.ObjectID
for i := range req.PhotographIDs {
photographID, parseErr := primitive.ObjectIDFromHex(req.PhotographIDs[i])
err = parseErr
if err != nil {
return
}
photographIDs = append(photographIDs, photographID)
}
var photographMod = &officialwebsitemod.Photograph{}
filter := bson.M{"_id": bson.M{"$in": photographIDs}}
update := bson.M{"$set": bson.M{"heroId": ownerID}}
data.Count, err = photographMod.Update(filter, update)
return
}
// ─────────────────────────────────────────
// Batch (Modify) types
// ─────────────────────────────────────────
type BatchUpdatePhotographOwnerReq struct {
PhotographIDs []string `json:"photograph_ids"`
OwnerID string `json:"owner_id"`
}
type BatchUpdatePhotographOwnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyPhotographReq struct {
ID string `json:"id" form:"id"`
HeroID *string `json:"heroID"`
Name *string `json:"name"`
SeoSlug *string `json:"seoSlug"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Photos *[]string `json:"photos"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeletePhotographReq struct {
ID string `json:"id" form:"id"`
}
type DeletePhotographResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyPhotographReq → model struct
// ─────────────────────────────────────────
func (req *ModifyPhotographReq) toMod() (data officialwebsitemod.Photograph, err error) {
if req == nil {
return
}
data = officialwebsitemod.Photograph{
Name: stringOrZero(req.Name),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
if req.HeroID != nil {
data.HeroID, err = primitive.ObjectIDFromHex(stringOrZero(req.HeroID))
if err != nil {
return
}
}
if req.Photos != nil {
data.Photos = *req.Photos
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return
}
// ─────────────────────────────────────────
// toM — ModifyPhotographReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyPhotographReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroId"] = heroID
}
}
if req.Name != nil {
set["name"] = *req.Name
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Photos != nil {
set["photos"] = *req.Photos
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type ListPhotographReq struct {
PageReq
ID *string `form:"id"`
HeroID *string `form:"heroID"`
Name *string `form:"name"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Photos *string `form:"photos"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (l *ListPhotographReq) Query() (filter officialwebsitemod.M, err error) {
filter = officialwebsitemod.M{}
if err = parseObjectIDFilter(filter, "_id", l.ID); err != nil {
return nil, err
}
if err = parseObjectIDFilter(filter, "heroId", l.HeroID); err != nil {
return nil, err
}
if l.Name != nil {
filter["name"] = *l.Name
}
if l.Description != nil {
filter["description"] = *l.Description
}
if l.Cover != nil {
filter["cover"] = *l.Cover
}
if err = parseJSONFilter(filter, "photos", l.Photos, &[]string{}); err != nil {
return nil, err
}
if l.Sort != nil {
filter["sort"] = *l.Sort
}
if l.IsActive != nil {
filter["isActive"] = *l.IsActive
}
if l.CreatedAt != nil {
filter["createdAt"] = *l.CreatedAt
}
if l.UpdatedAt != nil {
filter["updatedAt"] = *l.UpdatedAt
}
if l.DeletedAt != nil {
filter["deletedAt"] = *l.DeletedAt
}
return filter, nil
}
@@ -0,0 +1,221 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListRecruitForm(req *RecruitFormListReq) (int64, []officialwebsitemod.RecruitForm, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.RecruitForm{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateRecruitForm(req *ModifyRecruitFormReq) (officialwebsitemod.RecruitForm, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.RecruitForm{}, err
}
return data, nil
}
func UpdateRecruitForm(req *ModifyRecruitFormReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.RecruitForm{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteRecruitForm(req *DeleteRecruitFormReq) (data DeleteRecruitFormResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.RecruitForm{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyRecruitFormReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Sex *int `json:"sex"`
Age *int `json:"age"`
Country *string `json:"country"`
Address *string `json:"address"`
Contact *[]officialwebsitemod.ContactInfo `json:"contact"`
Description *string `json:"description"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteRecruitFormReq struct {
ID string `json:"id" form:"id"`
}
type DeleteRecruitFormResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyRecruitFormReq → model struct
// ─────────────────────────────────────────
func (req *ModifyRecruitFormReq) toMod() officialwebsitemod.RecruitForm {
if req == nil {
return officialwebsitemod.RecruitForm{}
}
data := officialwebsitemod.RecruitForm{
Name: stringOrZero(req.Name),
Sex: intOrZero(req.Sex),
Age: intOrZero(req.Age),
Country: stringOrZero(req.Country),
Address: stringOrZero(req.Address),
Description: stringOrZero(req.Description),
}
if req.Contact != nil {
data.Contact = *req.Contact
}
return data
}
// ─────────────────────────────────────────
// toM — ModifyRecruitFormReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyRecruitFormReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Sex != nil {
set["sex"] = *req.Sex
}
if req.Age != nil {
set["age"] = *req.Age
}
if req.Country != nil {
set["country"] = *req.Country
}
if req.Address != nil {
set["address"] = *req.Address
}
if req.Contact != nil {
set["contact"] = *req.Contact
}
if req.Description != nil {
set["description"] = *req.Description
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type RecruitFormListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Sex *int `form:"sex"`
Age *int `form:"age"`
Country *string `form:"country"`
Address *string `form:"address"`
Contact *string `form:"contact"`
Description *string `form:"description"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *RecruitFormListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Sex != nil {
filter["sex"] = *r.Sex
}
if r.Age != nil {
filter["age"] = *r.Age
}
if r.Country != nil {
filter["country"] = *r.Country
}
if r.Address != nil {
filter["address"] = *r.Address
}
if err := parseJSONFilter(filter, "contact", r.Contact, &[]officialwebsitemod.ContactInfo{}); err != nil {
return nil, err
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+113
View File
@@ -0,0 +1,113 @@
package officialwebsiteser
import (
"regexp"
"strings"
"91porn-server/common/stderr"
"github.com/mozillazg/go-pinyin"
)
var (
seoSlugValidRe = regexp.MustCompile(`^[a-z]+$`) // 纯小写英文字母
seoSlugStripRe = regexp.MustCompile(`[^a-z]`) // 非 a-z 一律剔除
)
// seoSlugMaxLen 自动生成 slug 的最大长度(按拼音音节边界截断,避免长标题生成超长 slug)
const seoSlugMaxLen = 20
// GenerateSeoSlug 从标题生成纯小写字母 slug:中文转拼音、英文保留、其余字符剔除,
// 并按音节边界累加到 seoSlugMaxLen 为止(不从音节中间截断)。
// 例: "优雅Summer 2023" -> "youyasummer"
func GenerateSeoSlug(title string) string {
if title == "" {
return ""
}
args := pinyin.NewArgs()
// 非汉字(英文/数字/符号)原样返回,后续再统一剔除非 a-z
args.Fallback = func(r rune, a pinyin.Args) []string {
return []string{string(r)}
}
rows := pinyin.Pinyin(title, args)
var b strings.Builder
for _, row := range rows {
if len(row) == 0 {
continue
}
// 单个音节先剔除非 a-z、转小写
syl := seoSlugStripRe.ReplaceAllString(strings.ToLower(row[0]), "")
if syl == "" {
continue
}
// 到长度上限则在音节边界停止,避免 slug 过长
if b.Len()+len(syl) > seoSlugMaxLen {
break
}
b.WriteString(syl)
}
return b.String()
}
// ValidateSeoSlug 校验 slug 格式:非空且纯小写英文字母。
func ValidateSeoSlug(s string) bool {
return seoSlugValidRe.MatchString(s)
}
// ResolveSeoSlug 统一处理 seoSlug
// - 手填(provided 非空):校验纯小写字母格式,并查重(冲突报错)
// - 留空:由 title 生成拼音雏形,冲突时追加后缀去重
//
// exists 由各表提供(查各自表是否已占用该 slug)。
func ResolveSeoSlug(provided, title string, exists func(slug string) (bool, error)) (string, error) {
if provided != "" {
if !ValidateSeoSlug(provided) {
return "", stderr.ErrParamError
}
used, err := exists(provided)
if err != nil {
return "", err
}
if used {
return "", stderr.ErrParamError
}
return provided, nil
}
return UniqueSeoSlug(GenerateSeoSlug(title), exists)
}
// UniqueSeoSlug 基于 base 生成同表唯一的 slugexists 判断某 slug 是否已被占用。
// 冲突时依次追加纯小写字母后缀 a、b…z、aa、ab…zz、aaa… 直到命中未占用值;
// base 为空则返回空(走稀疏索引,不参与唯一约束)。
func UniqueSeoSlug(base string, exists func(slug string) (bool, error)) (string, error) {
if base == "" {
return "", nil
}
// n=0 先试 base 原值,之后依次追加 a、b…z、aa… 后缀(双射 26 进制递增)。
// 表内记录有限且各候选互不相同,必能在有限次内命中未占用值。
for n := 0; ; n++ {
candidate := base
if n > 0 {
candidate += seoSlugSuffix(n)
}
used, err := exists(candidate)
if err != nil {
return "", err
}
if !used {
return candidate, nil
}
}
}
// seoSlugSuffix 返回第 n 个纯小写字母后缀(n>=1),按双射 26 进制递增:
// 1->a 2->b … 26->z 27->aa 28->ab … 52->az 53->ba … 702->zz 703->aaa
func seoSlugSuffix(n int) string {
var buf []byte
for n > 0 {
n--
buf = append([]byte{byte('a' + n%26)}, buf...)
n /= 26
}
return string(buf)
}
+202
View File
@@ -0,0 +1,202 @@
package officialwebsiteser
import (
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func ListTag(req *TagListReq) (int64, []officialwebsitemod.Tag, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Tag{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
func CreateTag(req *ModifyTagReq) (officialwebsitemod.Tag, error) {
data := req.toMod()
if err := data.Create(); err != nil {
return officialwebsitemod.Tag{}, err
}
return data, nil
}
func UpdateTag(req *ModifyTagReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Tag{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteTag(req *DeleteTagReq) (data DeleteTagResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Tag{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyTagReq struct {
ID string `json:"id" form:"id"`
Name *string `json:"name"`
Type *string `json:"type"`
IsHot *bool `json:"isHot"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteTagReq struct {
ID string `json:"id" form:"id"`
}
type DeleteTagResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyTagReq → model struct
// ─────────────────────────────────────────
func (req *ModifyTagReq) toMod() officialwebsitemod.Tag {
if req == nil {
return officialwebsitemod.Tag{}
}
return officialwebsitemod.Tag{
Name: stringOrZero(req.Name),
Type: stringOrZero(req.Type),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
}
}
// ─────────────────────────────────────────
// toM — ModifyTagReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyTagReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.Name != nil {
set["name"] = *req.Name
}
if req.Type != nil {
set["type"] = *req.Type
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type TagListReq struct {
PageReq
ID *string `form:"id"`
Name *string `form:"name"`
Type *string `form:"type"`
IsHot *bool `form:"isHot"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *TagListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if r.Name != nil {
filter["name"] = *r.Name
}
if r.Type != nil {
filter["type"] = *r.Type
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+652
View File
@@ -0,0 +1,652 @@
package officialwebsiteser
import (
"91porn-server/models/v/tagmod"
"91porn-server/models/v/vidmod"
"math/rand/v2"
"time"
"91porn-server/common/stderr"
officialwebsitemod "91porn-server/models/v/officialWebsitemod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ─────────────────────────────────────────
// Service functions
// ─────────────────────────────────────────
func CheckVideoExistByVid(vid []primitive.ObjectID) ([]officialwebsitemod.Video, error) {
filter := bson.M{"_id": bson.M{"$in": vid}}
mod := &officialwebsitemod.Video{}
videos, err := mod.FindMany(filter)
return videos, err
}
func ListVideo(req *VideoListReq) (int64, []officialwebsitemod.Video, error) {
filter, err := req.Query()
if err != nil {
return 0, nil, err
}
mod := &officialwebsitemod.Video{}
total, err := mod.Count(filter)
if err != nil {
return 0, nil, err
}
list, err := mod.FindMany(filter, req.FindOptions())
if err != nil {
return 0, nil, err
}
return total, list, nil
}
// resolveVideoSeoSlug 处理 seoSlug:手填则校验格式并查重(冲突报错);为空则由标题生成雏形并追加后缀去重。
func resolveVideoSeoSlug(provided, title string, excludeID primitive.ObjectID) (string, error) {
exists := func(slug string) (bool, error) {
filter := officialwebsitemod.M{"seoSlug": slug}
if !excludeID.IsZero() {
filter["_id"] = officialwebsitemod.M{"$ne": excludeID}
}
n, err := (&officialwebsitemod.Video{}).Count(filter)
return n > 0, err
}
if provided != "" {
if !ValidateSeoSlug(provided) {
return "", stderr.ErrParamError
}
used, err := exists(provided)
if err != nil {
return "", err
}
if used {
return "", stderr.ErrParamError
}
return provided, nil
}
return UniqueSeoSlug(GenerateSeoSlug(title), exists)
}
func CreateVideo(req *ModifyVideoReq) (officialwebsitemod.Video, error) {
data, err := req.toMod()
if err != nil {
return officialwebsitemod.Video{}, err
}
// SEO slug:手填优先(校验唯一),为空则由标题生成雏形并去重
if data.SeoSlug, err = resolveVideoSeoSlug(data.SeoSlug, data.Title, primitive.NilObjectID); err != nil {
return officialwebsitemod.Video{}, err
}
if err := data.Create(); err != nil {
return officialwebsitemod.Video{}, err
}
return data, nil
}
func UpdateVideo(req *ModifyVideoReq) (resp CountResp, err error) {
if req == nil || req.ID == "" {
return
}
id, parseErr := primitive.ObjectIDFromHex(req.ID)
if parseErr != nil || isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Video{}
if err = mod.FindOne(officialwebsitemod.M{"_id": id}); err != nil {
return
}
// 若提交了 seoSlug,先校验/去重(排除自身),回写后由 toM 落库
if req.SeoSlug != nil {
var slug string
if slug, err = resolveVideoSeoSlug(*req.SeoSlug, mod.Title, id); err != nil {
return
}
*req.SeoSlug = slug
}
var update officialwebsitemod.M
if update, err = req.toM(); err != nil || update == nil {
return
}
resp.Count, err = mod.Update(officialwebsitemod.M{"_id": id}, update)
return
}
func DeleteVideo(req *DeleteVideoReq) (data DeleteVideoResp, err error) {
if req == nil {
err = stderr.CodeEmptyData
return
}
var id primitive.ObjectID
if id, err = primitive.ObjectIDFromHex(req.ID); err != nil {
return
}
if isNilObjectID(id) {
err = stderr.ErrParamError
return
}
mod := &officialwebsitemod.Video{}
data.Count, err = mod.Delete(officialwebsitemod.M{"_id": id})
return
}
func BatchUpdateVideoOwner(req *BatchUpdateVideoOwnerReq) (data BatchUpdateVideoOwnerResp, err error) {
if req == nil {
err = stderr.ErrParamError
return
}
// Check owner is exist
var ownerID primitive.ObjectID
ownerID, err = primitive.ObjectIDFromHex(req.OwnerID)
if err != nil {
return
}
var heroMod = &officialwebsitemod.Hero{}
err = heroMod.FindOne(officialwebsitemod.M{"_id": ownerID})
if err != nil {
return
}
if isNilObjectID(heroMod.ID) {
err = stderr.OfficialWebsiteHeroExist
return
}
var update = bson.M{"$set": bson.M{"heroId": ownerID}}
// Check album is exist, if exist then add into update
if req.AlbumID != "" {
var albumID primitive.ObjectID
albumID, err = primitive.ObjectIDFromHex(req.AlbumID)
if err != nil {
return
}
var album = &officialwebsitemod.Album{}
err = album.FindOne(officialwebsitemod.M{"_id": albumID})
if err != nil {
return
}
update = bson.M{"$set": bson.M{"heroId": ownerID, "albumId": albumID}}
}
var videoIDs []primitive.ObjectID
for i := range req.VideoIDs {
videoID, parseErr := primitive.ObjectIDFromHex(req.VideoIDs[i])
err = parseErr
if err != nil {
return
}
videoIDs = append(videoIDs, videoID)
}
var videoMod = &officialwebsitemod.Video{}
filter := bson.M{"_id": bson.M{"$in": videoIDs}}
data.Count, err = videoMod.UpdateMany(filter, update)
return
}
// ─────────────────────────────────────────
// Batch (Modify) types
// ─────────────────────────────────────────
type BatchUpdateVideoOwnerReq struct {
VideoIDs []string `json:"video_ids"`
OwnerID string `json:"owner_id"`
AlbumID string `json:"album_id"`
}
type BatchUpdateVideoOwnerResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// Mutation (Modify) types
// ─────────────────────────────────────────
type ModifyVideoReq struct {
ID string `json:"id" form:"id"`
AlbumID *string `json:"albumId" form:"albumId"`
HeroID *string `json:"heroId" form:"heroId"`
Title *string `json:"title"`
Description *string `json:"description"`
Cover *string `json:"cover"`
Url *string `json:"url"`
SeoSlug *string `json:"seoSlug"`
Tags *[]officialwebsitemod.Tag `json:"tags"`
IsHot *bool `json:"isHot"`
Sort *int64 `json:"sort"`
IsActive *bool `json:"isActive"`
WatchCount *int64 `json:"watchCount"`
LikeCount *int64 `json:"likeCount"`
CommentCount *int64 `json:"commentCount"`
CollectCount *int64 `json:"collectCount"`
}
// ─────────────────────────────────────────
// Delete types
// ─────────────────────────────────────────
type DeleteVideoReq struct {
ID string `json:"id" form:"id"`
}
type DeleteVideoResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// toMod — ModifyVideoReq → model struct
// ─────────────────────────────────────────
func (req *ModifyVideoReq) toMod() (officialwebsitemod.Video, error) {
if req == nil {
return officialwebsitemod.Video{}, nil
}
data := officialwebsitemod.Video{
Title: stringOrZero(req.Title),
Description: stringOrZero(req.Description),
Cover: stringOrZero(req.Cover),
Url: stringOrZero(req.Url),
IsHot: boolOrZero(req.IsHot),
SortModel: officialwebsitemod.SortModel{
Sort: int64OrZero(req.Sort),
IsActive: boolOrZero(req.IsActive),
},
WatchCount: int64OrZero(req.WatchCount),
LikeCount: int64OrZero(req.LikeCount),
CommentCount: int64OrZero(req.CommentCount),
CollectCount: int64OrZero(req.CollectCount),
}
if req.AlbumID != nil {
if *req.AlbumID != "" {
albumID, err := primitive.ObjectIDFromHex(*req.AlbumID)
if err != nil || isNilObjectID(albumID) {
return officialwebsitemod.Video{}, stderr.ErrParamError
}
data.AlbumID = albumID
}
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return officialwebsitemod.Video{}, stderr.ErrParamError
}
data.HeroID = heroID
}
}
if req.Tags != nil {
data.Tags = *req.Tags
}
if req.SeoSlug != nil {
data.SeoSlug = *req.SeoSlug
}
return data, nil
}
// ─────────────────────────────────────────
// toM — ModifyVideoReq → officialwebsitemod.M
// ─────────────────────────────────────────
func (req *ModifyVideoReq) toM() (officialwebsitemod.M, error) {
if req == nil {
return nil, stderr.CodeEmptyData
}
set := officialwebsitemod.M{}
if req.AlbumID != nil {
if *req.AlbumID != "" {
albumID, err := primitive.ObjectIDFromHex(*req.AlbumID)
if err != nil || isNilObjectID(albumID) {
return nil, stderr.ErrParamError
}
set["albumId"] = albumID
}
}
if req.HeroID != nil {
if *req.HeroID != "" {
heroID, err := primitive.ObjectIDFromHex(*req.HeroID)
if err != nil || isNilObjectID(heroID) {
return nil, stderr.ErrParamError
}
set["heroId"] = heroID
}
}
if req.Title != nil {
set["title"] = *req.Title
}
if req.Description != nil {
set["description"] = *req.Description
}
if req.Cover != nil {
set["cover"] = *req.Cover
}
if req.Url != nil {
set["url"] = *req.Url
}
if req.Tags != nil {
set["tags"] = *req.Tags
}
if req.SeoSlug != nil && *req.SeoSlug != "" {
set["seoSlug"] = *req.SeoSlug
}
if req.IsHot != nil {
set["isHot"] = *req.IsHot
}
if req.Sort != nil {
set["sort"] = *req.Sort
}
if req.IsActive != nil {
set["isActive"] = *req.IsActive
}
if req.WatchCount != nil {
set["watchCount"] = *req.WatchCount
}
if req.LikeCount != nil {
set["likeCount"] = *req.LikeCount
}
if req.CommentCount != nil {
set["commentCount"] = *req.CommentCount
}
if req.CollectCount != nil {
set["collectCount"] = *req.CollectCount
}
if len(set) == 0 {
return nil, nil
}
set["updatedAt"] = time.Now().UTC()
return officialwebsitemod.M{"$set": set}, nil
}
func BatchImportVideo(req *BatchImportVideoReq) (resp BatchImportVideoResp, err error) {
if req == nil || len(req.VideoIds) == 0 {
err = stderr.ErrParamError
return
}
objIds := make([]primitive.ObjectID, 0, len(req.VideoIds))
videoIDSet := make(map[primitive.ObjectID]struct{}, len(req.VideoIds))
for _, videoId := range req.VideoIds {
var id primitive.ObjectID
id, err = primitive.ObjectIDFromHex(videoId)
if err != nil {
return
}
if _, exists := videoIDSet[id]; exists {
continue
}
videoIDSet[id] = struct{}{}
objIds = append(objIds, id)
}
if len(objIds) == 0 {
return
}
// 1) Filter out videos that already exist in official website video table by `_id`.
var videoMod = &officialwebsitemod.Video{}
officialVideos, findVideoErr := videoMod.FindMany(officialwebsitemod.M{"_id": bson.M{"$in": objIds}})
if findVideoErr != nil {
err = findVideoErr
return
}
existedVideoMap := make(map[primitive.ObjectID]struct{}, len(officialVideos))
for _, v := range officialVideos {
existedVideoMap[v.ID] = struct{}{}
}
needImportVideoIDs := make([]primitive.ObjectID, 0, len(objIds))
for _, id := range objIds {
if _, exists := existedVideoMap[id]; exists {
continue
}
needImportVideoIDs = append(needImportVideoIDs, id)
}
if len(needImportVideoIDs) == 0 {
return
}
mats := []vidmod.Matcher{
(&vidmod.IDInMatch{IDs: needImportVideoIDs}).New(),
}
list, err := vidmod.FindMany(mats...)
if err != nil {
return
}
if len(list) == 0 {
return
}
var insertVideos []officialwebsitemod.Video
allTagIDs := make([]primitive.ObjectID, 0)
allTagIDSet := make(map[primitive.ObjectID]struct{})
for _, video := range list {
insertVideos = append(insertVideos, officialwebsitemod.Video{
ID: video.ID,
Title: video.Title,
Description: video.Content,
Cover: video.Cover,
Url: video.SourceURL,
IsHot: false,
WatchCount: int64(video.FakePlayCount+video.PlayCount) * rand.Int64N(10),
LikeCount: int64(video.FakeLikeCount+video.LikeCount) * rand.Int64N(10),
CommentCount: int64(video.FakeCommentCount+video.CommentCount) * rand.Int64N(10),
CollectCount: int64(video.CollectCount) * rand.Int64N(10),
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
BaseModel: officialwebsitemod.BaseModel{
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
})
for _, tag := range video.Tags {
if _, exists := allTagIDSet[tag]; exists {
continue
}
allTagIDSet[tag] = struct{}{}
allTagIDs = append(allTagIDs, tag)
}
}
if len(insertVideos) == 0 {
return
}
// 2) Filter out tags that already exist in official website tag table by `_id`.
var tagMod = &officialwebsitemod.Tag{}
existedTagMap := map[primitive.ObjectID]struct{}{}
existedOfficialTagInfoMap := map[primitive.ObjectID]officialwebsitemod.Tag{}
if len(allTagIDs) > 0 {
officialWebsiteTags, findTagErr := tagMod.FindMany(officialwebsitemod.M{
"_id": bson.M{"$in": allTagIDs},
"type": officialwebsitemod.OfficialWebsiteTagTypeVideo,
})
if findTagErr != nil {
err = findTagErr
return
}
existedTagMap = make(map[primitive.ObjectID]struct{}, len(officialWebsiteTags))
for _, t := range officialWebsiteTags {
existedTagMap[t.ID] = struct{}{}
existedOfficialTagInfoMap[t.ID] = t
}
}
needImportTagIDs := make([]primitive.ObjectID, 0, len(allTagIDs))
for _, tagID := range allTagIDs {
if _, exists := existedTagMap[tagID]; exists {
continue
}
needImportTagIDs = append(needImportTagIDs, tagID)
}
tagsMap, err := tagmod.FindTagsMapByIDS(needImportTagIDs)
if err != nil {
return
}
var insertTags []officialwebsitemod.Tag
for _, tagID := range needImportTagIDs {
tag, ok := tagsMap[tagID]
if !ok {
continue
}
insertTags = append(insertTags, officialwebsitemod.Tag{
ID: tag.ID,
Name: tag.TagName,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: len(tag.HotMark) > 0,
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
BaseModel: officialwebsitemod.BaseModel{
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
})
}
// Fill video tags from tag info map to keep video docs self-contained.
for i := range insertVideos {
srcVideo := list[i]
videoTags := make([]officialwebsitemod.Tag, 0, len(srcVideo.Tags))
for _, tagID := range srcVideo.Tags {
if oldTag, exists := existedOfficialTagInfoMap[tagID]; exists {
videoTags = append(videoTags, officialwebsitemod.Tag{
ID: oldTag.ID,
Name: oldTag.Name,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: oldTag.IsHot,
SortModel: officialwebsitemod.SortModel{
Sort: oldTag.Sort,
IsActive: true,
},
})
continue
}
tag, ok := tagsMap[tagID]
if !ok {
continue
}
videoTags = append(videoTags, officialwebsitemod.Tag{
ID: tag.ID,
Name: tag.TagName,
Type: officialwebsitemod.OfficialWebsiteTagTypeVideo,
IsHot: len(tag.HotMark) > 0,
SortModel: officialwebsitemod.SortModel{
IsActive: true,
},
})
}
insertVideos[i].Tags = videoTags
}
// 3) Insert filtered videos and tags into official website tables separately.
var ids []primitive.ObjectID
ids, err = videoMod.InsertMany(insertVideos)
if err != nil {
return
}
for i := range insertTags {
if createErr := insertTags[i].Create(); createErr != nil {
err = createErr
return
}
}
resp.Count = int64(len(ids))
return
}
// ─────────────────────────────────────────
// Batch Import types
// ─────────────────────────────────────────
type BatchImportVideoReq struct {
VideoIds []string `json:"videoIds"`
}
type BatchImportVideoResp struct {
Count int64 `json:"count"`
}
// ─────────────────────────────────────────
// List types
// ─────────────────────────────────────────
type VideoListReq struct {
PageReq
ID *string `form:"id"`
IDs []string `form:"ids"`
PrimiteveIDs []primitive.ObjectID `form:"primitiveIds"`
AlbumID *string `form:"albumId"`
HeroID *string `form:"heroID"`
Title *string `form:"title"`
Description *string `form:"description"`
Cover *string `form:"cover"`
Url *string `form:"url"`
Tags *string `form:"tags"`
IsHot *bool `form:"isHot"`
Sort *int64 `form:"sort"`
IsActive *bool `form:"isActive"`
CreatedAt *time.Time `form:"createdAt"`
UpdatedAt *time.Time `form:"updatedAt"`
DeletedAt *time.Time `form:"deletedAt"`
}
func (r *VideoListReq) Query() (officialwebsitemod.M, error) {
filter := officialwebsitemod.M{}
if err := parseObjectIDFilter(filter, "_id", r.ID); err != nil {
return nil, err
}
if len(r.IDs) > 0 {
var objIDs []primitive.ObjectID
for _, id := range r.IDs {
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
objIDs = append(objIDs, objID)
}
filter["_id"] = bson.M{"$in": objIDs}
}
if len(r.PrimiteveIDs) > 0 {
filter["_id"] = bson.M{"$in": r.PrimiteveIDs}
}
if err := parseObjectIDFilter(filter, "albumId", r.AlbumID); err != nil {
return nil, err
}
if err := parseObjectIDFilter(filter, "heroId", r.HeroID); err != nil {
return nil, err
}
if r.Title != nil {
filter["title"] = *r.Title
}
if r.Description != nil {
filter["description"] = *r.Description
}
if r.Cover != nil {
filter["cover"] = *r.Cover
}
if r.Url != nil {
filter["url"] = *r.Url
}
if err := parseJSONFilter(filter, "tags", r.Tags, &[]officialwebsitemod.Tag{}); err != nil {
return nil, err
}
if r.IsHot != nil {
filter["isHot"] = *r.IsHot
}
if r.Sort != nil {
filter["sort"] = *r.Sort
}
if r.IsActive != nil {
filter["isActive"] = *r.IsActive
}
if r.CreatedAt != nil {
filter["createdAt"] = *r.CreatedAt
}
if r.UpdatedAt != nil {
filter["updatedAt"] = *r.UpdatedAt
}
if r.DeletedAt != nil {
filter["deletedAt"] = *r.DeletedAt
}
return filter, nil
}
+45
View File
@@ -0,0 +1,45 @@
package officialser
import (
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/models/v/officialmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// QueryAll 查询列表
func QueryAll(in *officialmod.QueryAllCond) ([]*officialmod.OfficialConfig, stderr.Code) {
list, err := officialmod.QueryAll(in, in.Options())
if err != nil {
return nil, stderr.ErrDbQueryError
}
return list, stderr.Success
}
// UpdateOfficial 编辑官方配置
func UpdateOfficial(officialID primitive.ObjectID, doc officialmod.OfficiaUpdateDoc) (err error) {
tMod, err := officialmod.FindOneTagByID(officialID)
if err != nil {
log.Error("web service Official UpdateOfficial FindOneTagByID fail error", log.E(err), log.Any("officialID", officialID))
return
}
if tMod.ID.IsZero() {
log.Error("web service Official UpdateOfficial Official is null", log.E(err), log.Any("officialID", officialID))
return
}
return officialmod.UpdateOneOfficiaByID(officialID, doc)
}
// AddOfficial 添加标签
func AddOfficial(official officialmod.OfficialConfig) (data interface{}, err error) {
return officialmod.InsertOne(&official)
}
// DelOfficial 删除标签
func DelOfficial(tag officialmod.WebOfficialDeleteReqInfo) (err error) {
_, err = officialmod.DeleteOfficias(tag.IDs)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,39 @@
package operationlogser
import (
"91porn-server/common/stderr"
"91porn-server/models/v/operationlogmod"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func GetOperationLogList(req GetOperationLogListReq) (*GetOperationLogListResp, stderr.Code) {
data := new(GetOperationLogListResp)
filter := bson.M{}
if req.UserID != nil && *req.UserID > 0 {
filter["userID"] = *req.UserID
}
if req.CreatedUser != nil && len(*req.CreatedUser) > 0 {
filter["createdUser"] = *req.CreatedUser
}
if req.Content != nil && len(*req.Content) > 0 {
filter["afterContent"] = *req.Content
}
if req.Start != nil && !req.Start.IsZero() && req.End != nil && !req.End.IsZero() {
filter["createdAt"] = bson.M{"$gte": req.Start, "$lt": req.End}
}
if req.OperationType != nil && *req.OperationType > 0 {
filter["operationType"] = *req.OperationType
}
count := operationlogmod.Count(filter)
data.Total = count
opts := (&options.FindOptions{}).
SetSort(bson.D{{Key: "createdAt", Value: -1}}).SetLimit(int64(req.PageSize)).SetSkip(int64((req.PageNumber - 1) * req.PageSize))
operationlogList, serr := operationlogmod.FindManyOperationLog(filter, opts)
if serr != nil {
return data, stderr.ErrDbQueryError
}
data.List = operationlogList
return data, stderr.Success
}
+25
View File
@@ -0,0 +1,25 @@
package operationlogser
import (
"time"
"91porn-server/models/commod"
"91porn-server/models/v/operationlogmod"
)
// GetOperationLogListReq 获取帖子、用户操作日志列表 请求数据
type GetOperationLogListReq struct {
UserID *int64 `form:"userID,omitempty" json:"userID,omitempty" bson:"userID,omitempty"` //用户ID
OperationType *int `form:"operationType,omitempty" json:"operationType,omitempty" bson:"operationType,omitempty"` //操作类型
Content *string `form:"content,omitempty" json:"content,omitempty" bson:"content,omitempty"` //操作内容
CreatedUser *string `form:"createdUser,omitempty" json:"createdUser,omitempty" bson:"createdUser,omitempty"` //操作人
Start *time.Time `form:"start,omitempty" json:"start,omitempty" bson:"start,omitempty"` //开始时间
End *time.Time `form:"end,omitempty" json:"end,omitempty" bson:"end,omitempty"` //结束时间
commod.Page
}
// GetOperationLogListResp 获取帖子、用户操作日志列表 回复数据
type GetOperationLogListResp struct {
Total int64 `json:"total" bson:"total"`
List []operationlogmod.OperationLog `json:"list" bson:"list"`
}

Some files were not shown because too many files have changed in this diff Show More