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
+372
View File
@@ -0,0 +1,372 @@
package ai_text_to_image_ser
import (
"91porn-server/app/appg"
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/middleware/ua"
"91porn-server/models/cache/aitexttoimagedata"
"91porn-server/models/cache/sysconfdata"
"91porn-server/models/commod"
"91porn-server/models/v/aichangefacevidmod"
"91porn-server/models/v/aitexttoimagemod"
"91porn-server/models/v/sysconfmod"
"91porn-server/models/v/txnmod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/walletmod"
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type AppQueryListReq struct {
Status int `json:"status" form:"status"` // 状态 1、排队中 2、生成成功 3、生成失败
UID uint64 `json:"-"` // 用户ID
commod.Page
}
func (p *AppQueryListReq) Filter() primitive.M {
filter := bson.M{}
switch p.Status {
case 1:
filter["status"] = bson.M{"$in": []aitexttoimagemod.AiTextToImageStatus{aitexttoimagemod.StatusOrderSuccess, aitexttoimagemod.StatusSubmitted}}
case 2:
filter["status"] = aitexttoimagemod.StatusGenerationSuccess
case 3:
filter["status"] = bson.M{"$in": []aitexttoimagemod.AiTextToImageStatus{aitexttoimagemod.StatusGenerationFailed, aitexttoimagemod.StatusRefunded}}
}
filter["uid"] = p.UID
filter["isHide"] = false
return filter
}
type AppListRes struct {
Total int64 `json:"total"`
HasNext bool `json:"hasNext"`
List []*aitexttoimagemod.AiTextToImageInfo `json:"list"`
}
// GetList 获取列表
func (p *AppQueryListReq) GetList() AppListRes {
var res AppListRes
var err error
sort := bson.D{{"createdAt", -1}}
// 获取列表
var data []aitexttoimagemod.AiTextToImage
data, res.Total, res.HasNext, err = aitexttoimagemod.GetList(p.Filter(), int64(p.Skip()), int64(p.Limit()), sort)
if err != nil {
log.Error("获取AI绘图列表列表数据错误", log.Any("Params", *p), log.E(err))
return res
}
res.List = aitexttoimagedata.FormatAppDataList(data)
// 获取所有的模版
cond := bson.M{"status": 1, "moduleType": 5}
mods, err := aichangefacevidmod.GetAllModsByCond(cond)
if err != nil {
return res
}
if len(mods) > 0 {
for i := range res.List {
for _, m := range mods {
if res.List[i].StyleType == m.StyleType {
res.List[i].StyleUrl = m.Cover
}
}
}
}
return res
}
type GenerateReq struct {
Text string `json:"text"` // 绘图描述
StyleType int `json:"styleType"` // 绘图风格
AspectRatio string `json:"aspectRatio"` // 绘图比例 1:1,4:3,3:4,16:9,9:16
Coin int64 `json:"-"` // 此次脱衣金币个数
UID uint64 `json:"-"` // 用户ID
ShareTitle string `json:"shareTitle"` // 分享标题
ShareStatus int `json:"shareStatus"` // 是否分享 0-不分享 1-分享
}
func (p *GenerateReq) GenerateInfo(orderId primitive.ObjectID, orderCreatedAt time.Time, debitCoins, debitIncomeCoins int64) aitexttoimagemod.AiTextToImage {
return aitexttoimagemod.AiTextToImage{
ID: orderId,
Coin: debitIncomeCoins + debitCoins,
DebitAmountCoin: debitCoins,
DebitIncomeCoin: debitIncomeCoins,
IsFreeTimes: false,
Text: p.Text,
StyleType: p.StyleType,
AspectRatio: p.AspectRatio,
UID: p.UID,
Status: int(aitexttoimagemod.StatusOrderSuccess),
IsHide: false,
ShareTitle: p.ShareTitle,
ShareStatus: p.ShareStatus,
UpdatedAt: orderCreatedAt,
CreatedAt: orderCreatedAt,
}
}
// Generate 生成订单
func (p *GenerateReq) Generate(ua ua.UA, ip string) (stderr.Code, error) {
// 获取用户信息
user, err := usermod.FindUserByUID(p.UID)
if err != nil {
return stderr.ErrDbQueryError, err
}
if user.ID.IsZero() {
return stderr.UserIsNotExists, errors.New("user is null")
}
configure, _ := sysconfdata.GetAllFromCache()
p.Coin = configure.GetInt(sysconfmod.VCodeAiTextToImagePrice)
var debitAmountCoins, debitIncomeCoins int64
if p.Coin > 0 {
// 获取用户钱包
w, err := walletmod.GetWallet(p.UID)
if err != nil {
return stderr.ErrDbQueryError, err
}
if w == nil {
return stderr.InsufficientBalance, errors.New("Insufficient balance")
}
// 如果收费的部分余额不够支付
if p.Coin > (w.Amount + w.Income) {
return stderr.InsufficientBalance, errors.New("Insufficient balance")
}
debitAmountCoins, debitIncomeCoins = TotalDebit(p.Coin, *w)
}
// 获取用户是否是复购
isRepurchase, err := txnmod.CheckRepurchaseByTransTypes(p.UID, []txnmod.TransType{
txnmod.AiTextToImageDebitGold,
txnmod.AiTextToImageDebitInComeGold,
})
if err != nil {
log.Error("txnmod.CheckRepurchaseByTransTypes fail", log.E(err))
return stderr.ErrDbQueryError, err
}
var orderId primitive.ObjectID
var orderCreatedAt time.Time
if err = appg.VideoDB.Trans(func(tool *db.MongoTool) error {
var tl []txnmod.TransactionLog
if debitIncomeCoins > 0 || debitAmountCoins > 0 {
// 扣除钱包余额
wallet, wErr := walletmod.DebitAmountAndIncome(tool, debitAmountCoins, debitIncomeCoins, p.UID)
if wErr != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Generate walletmod.DebitAmount error:%+v:", wErr), log.Any("uid", p.UID))
return stderr.ErrDbQueryError
}
if debitAmountCoins > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: p.UID,
Amount: -debitAmountCoins,
ActualAmount: float64(-debitAmountCoins),
TranType: txnmod.AiTextToImageDebitGold.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitGold),
Desc: fmt.Sprintf("生成AI绘图金币-%d", debitAmountCoins),
RealAmount: walletmod.GetRealAmount(wallet),
SysType: user.SysType,
IsRepurchase: isRepurchase,
})
}
if debitIncomeCoins > 0 {
tl = append(tl, txnmod.TransactionLog{
TransNo: primitive.NewObjectID(),
UID: p.UID,
Amount: -debitIncomeCoins,
ActualAmount: float64(-debitIncomeCoins),
TranType: txnmod.AiTextToImageDebitInComeGold.Key(),
TranTypeInt: int64(txnmod.AiTextToImageDebitInComeGold),
Desc: fmt.Sprintf("生成AI绘图收益金币-%d", debitIncomeCoins),
RealAmount: walletmod.GetRealAmount(wallet),
SysType: user.SysType,
IsRepurchase: isRepurchase,
})
}
}
if len(tl) > 0 {
txnErr := txnmod.InsertManyTransactionLog(tool, tl)
if txnErr != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Generate txnmod.InsertTransactionLog error:%+v:", txnErr), log.Any("uid", p.UID))
return stderr.ErrDbQueryError
}
}
// 新增AI图生视频记录
orderId = primitive.NewObjectID()
orderCreatedAt = time.Now()
order := p.GenerateInfo(orderId, orderCreatedAt, debitAmountCoins, debitIncomeCoins)
if _, err = aitexttoimagemod.Insert(tool, order); err != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Generate InsertOnes error:%+v:", err), log.Any("uid", p.UID))
return stderr.ErrDbInsertError
}
return nil
}); err != nil {
log.Error(fmt.Sprintf("Handle imagetovideo Generate Trans error:%+v;uid:%v;", err, p.UID))
return stderr.ErrDbUpdateError, err
}
common.Go(func() {
autoUploadOrder(orderId)
})
return stderr.Success, nil
}
type AutoUploadReq struct {
AppID int `json:"appId"` // 应用 ID
UID uint64 `json:"uid"` // 用户 ID
Text string `json:"text"` // 绘图描述
StyleType int `json:"styleType"` // 绘图风格
AspectRatio string `json:"aspectRatio"` // 绘图比例 1:1,4:3,3:4,16:9,9:16
AppOrderNum string `json:"appOrderNum"` // 应用订单号
NotifyURL string `json:"notifyUrl"` // 通知 URL
}
func autoUploadOrder(id primitive.ObjectID) error {
item, err := aitexttoimagemod.GetInfo(id)
if err != nil {
return err
}
if item.ID.IsZero() {
return errors.New(" aiimagetovideo order is null")
}
// 将请求参数编码为JSON
requestBody := AutoUploadReq{
AppID: int(commod.KFK_APPID),
UID: item.UID,
Text: item.Text,
AspectRatio: item.AspectRatio,
StyleType: item.StyleType,
AppOrderNum: item.ID.Hex(),
NotifyURL: fmt.Sprintf("%v/api/web/admin/ai/text_to_image/callback", appg.Conf.URL.AiImageToVideoCallbackUrl),
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
return err
}
apiURL := getAutoUploadUrl()
// 创建请求对象
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("创建请求失败: %v", err)
}
// 通过http.Client发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
// 读取响应内容
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取响应内容失败: %v", err)
}
// 如果返回状态码非200,可以考虑返回错误
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("请求失败,状态码:%d,内容:%s", resp.StatusCode, string(respBytes))
}
// 更新订单
updateCond := bson.M{
"status": aitexttoimagemod.StatusSubmitted,
"updatedAt": time.Now(),
}
_, err = aitexttoimagemod.UpdateByID(nil, id, updateCond)
if err != nil {
return err
}
return nil
}
func getAutoUploadUrl() string {
return fmt.Sprintf("%v/api/comfyui/prd/create_text_job", appg.Conf.URL.AiImageToVideoUrl)
}
func TotalDebit(price int64, w walletmod.Wallet) (amountCoins, incomeCoins int64) {
var (
debitAmountCoins int64
debitIncomeCoins int64
)
// 计算当日实际扣减的金额
debitCoins := price
if w.Amount >= debitCoins {
debitAmountCoins = debitCoins
} else {
debitAmountCoins = w.Amount
debitIncomeCoins = debitCoins - w.Amount
}
return debitAmountCoins, debitIncomeCoins
}
type HideReq struct {
ID string `json:"id"` // AI订单ID
UID uint64 `json:"-"` // 用户ID
}
// Hide 删除订单
func (p *HideReq) Hide() error {
oid, err := primitive.ObjectIDFromHex(p.ID)
if err != nil {
return err
}
item, err := aitexttoimagemod.GetInfo(oid)
if err != nil {
log.Error("获取AI绘图列表详情数据错误", log.Any("ID", p.ID), log.E(err))
return err
}
if item.ID.IsZero() {
return errors.New("AI订单不存在")
}
if item.UID != p.UID {
return errors.New("只能删除自己的订单")
}
if err = validateHideStatus(item.Status); err != nil {
return err
}
if err = aitexttoimagemod.Hide(p.UID, oid); err != nil {
return err
}
return nil
}
func validateHideStatus(status int) error {
if status == int(aitexttoimagemod.StatusOrderSuccess) ||
status == int(aitexttoimagemod.StatusSubmitted) {
return stderr.AiGenningDelForbidden
}
return nil
}