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()}) }