Files
huangguo_server/web/api/exportctrl/export.go
T
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

559 lines
17 KiB
Go

package exportctrl
import (
"91porn-server/common/timeutil"
"91porn-server/models/v/export_task_mod"
"fmt"
"net/http"
"path/filepath"
"strconv"
"sync"
"time"
"91porn-server/common"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/common/timeutil/timerange"
"91porn-server/models/commod"
"91porn-server/models/l/playlgmod"
"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/vidmod"
"91porn-server/models/v/wdordmod"
"91porn-server/web/service/exchcodeser"
"91porn-server/web/service/exportser"
"91porn-server/web/service/withdrawser"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type userListReq struct {
usermod.UserListSelector
common.StandQuery
IsPretendAcc *int `form:"isPretendAcc" json:"isPretendAcc"` //是否马甲账号
}
// Export doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/:fileType [get]
func Export(c *gin.Context) {
typeFile := c.Param("fileType")
t, err := strconv.ParseInt(typeFile, 10, 64)
if typeFile == "" || err != nil {
common.ServeJSON(c, stderr.ErrParamError, err.Error())
return
}
fpath, err := exportser.Export(t)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(c, stderr.ErrExportFileFail, err.Error())
return
}
c.Writer.WriteHeader(http.StatusOK)
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
c.Header("Content-Type", "application/octet-stream")
c.File(fpath)
}
func getMuchUser(arg userListReq) []*usermod.User {
uInfo := []*usermod.User{}
cond, opt := common.StandQueryMap(arg.StandQuery, arg.UserListSelector)
//马甲账户
if arg.IsPretendAcc != nil && *arg.IsPretendAcc == 1 {
cond["devID"] = bson.M{"$regex": usermod.SystemDevIDPrex, "$options": "i"}
}
//total, err := usermod.GetTotalCnt(cond, opt)
//if err != nil {
// return uInfo
//}
// 每次最多导出1000条
uInfo, _ = usermod.FindMany(cond, opt, int64(arg.PageNumber), int64(1000))
return uInfo
//if total < 1000 {
// uInfo, err = usermod.FindMany(cond, opt, int64(0), int64(1000))
// return uInfo
//}
////创建多个协程去拉取
//const goRoutineCnt int = 60
//var wg sync.WaitGroup
//size := total / int64(goRoutineCnt)
//wg.Add(goRoutineCnt)
//var mData [goRoutineCnt][]*usermod.User
//for i := 0; i < goRoutineCnt-1; i++ {
// common.GoParam(i, func(i int) {
// defer wg.Done()
// skip := int64(i) * size
// mData[i], err = usermod.FindMany(cond, opt, int64(skip), int64(size))
// })
//}
//common.Go(func() {
// defer wg.Done()
// skip := int64(goRoutineCnt-1) * size
// mData[goRoutineCnt-1], err = usermod.FindMany(cond, opt, int64(skip), int64(size+size))
//})
//wg.Wait()
//for i := 0; i < goRoutineCnt; i++ {
// uInfo = append(uInfo, mData[i]...)
//}
//return uInfo
}
func setArray2Map(datas []playlgmod.UIDCount) map[uint64]int {
m := make(map[uint64]int)
for _, d := range datas {
m[d.UID] = d.Count
}
return m
}
func calWatchedVideoCnt(uids []uint64) []playlgmod.UIDCount {
le := len(uids)
if le < 1000 {
datas, _ := playlgmod.HasWatchedVideoCnt(uids)
return datas
}
//创建多个协程去拉取
const goRoutineCnt = 10
var wg sync.WaitGroup
var mData [goRoutineCnt][]playlgmod.UIDCount
size := le / goRoutineCnt
wg.Add(goRoutineCnt)
for i := 0; i < goRoutineCnt-1; i++ {
common.GoParam(i, func(i int) {
defer wg.Done()
begin := i * size
end := begin + size - 1
mData[i], _ = playlgmod.HasWatchedVideoCnt(uids[begin:end])
})
}
common.Go(func() {
defer wg.Done()
begin := (goRoutineCnt - 1) * size
end := le - 1
mData[goRoutineCnt-1], _ = playlgmod.HasWatchedVideoCnt(uids[begin:end])
})
wg.Wait()
datas := []playlgmod.UIDCount{}
for i := 0; i < goRoutineCnt; i++ {
datas = append(datas, mData[i]...)
}
return datas
}
//
//func encodeUsers(users []*usermod.User, videoCntMap map[uint64]int, unDealVideoCntMap map[uint64]int) []usermod.ExportUser {
// usersLen := len(users)
// uids := make([]uint64, 0, usersLen)
// uInfos := make([]usermod.ExportUser, 0, usersLen)
// for _, u := range users {
// if u != nil {
// user := usermod.ExportUser{
// UID: u.UID,
// DevID: u.DevID,
// DevType: u.DevType,
// RegisterIP: u.RegisterIP,
// Mobile: u.Mobile,
// Gender: u.Gender,
// Channel: u.DistrictCode,
// Name: u.Name,
// PromotionCode: u.PromCode,
// Summary: u.Summary,
// Region: u.Region,
// Birthday: u.Birthday,
// VipLevel: u.VipLevel,
// VipExpireDate: u.VipExpireDate,
// CreatedAt: u.CreatedAt,
// MobileBindAt: u.MobileBindAt,
// HasLocked: u.HasLocked,
// HasBanned: u.HasBanned,
// TotalVideoCnt: videoCntMap[u.UID],
// UndealVideoCnt: unDealVideoCntMap[u.UID],
// LastVisitAt: u.LastVisitAt,
// }
// uInfos = append(uInfos, user)
// uids = append(uids, u.UID)
// }
// }
// wCnt := setArray2Map(calWatchedVideoCnt(uids))
// for i, u := range uInfos {
// uInfos[i].WatchCount = wCnt[u.UID]
// }
// return uInfos
//}
func getUIDs(users []*usermod.User) []uint64 {
uids := make([]uint64, 0, len(users))
for _, v := range users {
if v == nil {
continue
}
uids = append(uids, v.UID)
}
return uids
}
// Export doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/:fileType [get]
func UserList(c *gin.Context) {
manager, err := common.GetAdminAct(c)
if err != nil {
common.ServeJSON(c, stderr.AdminIDErr, err.Error())
return
}
var arg userListReq
if err := c.ShouldBind(&arg); err != nil {
common.ServeJSON(c, stderr.ErrParamError, "user List arg error "+err.Error())
return
}
if code := IsWithinWeek(arg.StartTime, arg.EndTime); code != stderr.Success {
common.ServeJSON(c, code, code)
return
}
err = export_task_mod.CreateTask(export_task_mod.ExportUserTask, manager, arg)
if err != nil {
common.ServeJSON(c, stderr.ErrExportFileFail, "")
return
}
common.ServeJSON(c, stderr.Success, "成功")
}
// IsWithinWeek 判断是否是一周内
func IsWithinWeek(start *string, end *string) (code stderr.Code) {
if start == nil || end == nil {
return stderr.ErrTimeIsEmpty
}
startTime := timeutil.StrTimeToTime(*start)
endTime := timeutil.StrTimeToTime(*end)
hours := endTime.Sub(startTime).Hours()
if hours > 24*7 {
return stderr.ErrTimeWithinWeek
}
return stderr.Success
}
// RechargeOrder doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/rechargeOrder [get]
func RechargeOrder(ctx *gin.Context) {
var arg struct {
common.StandQuery
rchgordmod.RchgQueryReq
}
if err := ctx.ShouldBind(&arg); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err)
return
}
//导出条数写死
arg.StandQuery.PageNumber = 1
arg.StandQuery.PageSize = 100000
_, data, err := rchgordmod.FindRechargesOrders(common.StandQueryMap(arg.StandQuery, arg.RchgQueryReq))
if err != nil {
common.ServeJSON(ctx, stderr.ErrDbQueryError, "")
return
}
exptData := make([]*rchgordmod.RchargeOrderExport, len(data))
_100 := decimal.NewFromInt(100)
for i, v := range data {
exptData[i] = &rchgordmod.RchargeOrderExport{
ID: v.ID,
UID: v.UID,
DevID: v.DevID,
OID: v.OID,
UserIP: v.UserIP,
Name: v.Name,
Tel: v.Tel,
PayAct: v.PayAct,
DevType: v.DevType,
Amount: decimal.NewFromInt(v.Amount).Div(_100).StringFixed(4),
Money: decimal.NewFromInt(v.Money).Div(_100).StringFixed(4),
PayMoney: decimal.NewFromInt(v.PayMoney).Div(_100).StringFixed(4),
RechargeType: v.RechargeType,
ProductID: v.ProductID,
VipID: v.VipID,
Channel: v.Channel,
Status: v.Status,
StatusDesc: v.StatusDesc,
ProgressAt: v.ProgressAt,
FailureAt: v.FailureAt,
SuccessAt: v.SuccessAt,
PaymentAt: v.PaymentAt,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
SourcePage: v.SourcePage,
SourceRef: v.SourceRef,
VideoID: v.VideoID,
ActivityID: v.ActivityID,
ExperimentID: v.ExperimentID,
ExperimentVariant: v.ExperimentVariant,
SessionID: v.SessionID,
}
}
fpath, err := exportser.RechargeOrderExport(exptData)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}
// WithdrawOrder doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/withdrawOrder [get]
func WithdrawOrder(ctx *gin.Context) {
var arg struct {
common.StandQuery
wdordmod.WithdrawWebQueryReq
}
if err := ctx.ShouldBind(&arg); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err)
return
}
//导出条数写死
arg.StandQuery.PageNumber = 1
arg.StandQuery.PageSize = 100000
_, data, err := withdrawser.GetAllOrders(common.StandQueryMap(arg.StandQuery, arg.WithdrawWebQueryReq))
if err != nil {
common.ServeJSON(ctx, stderr.ErrDbQueryError, "")
return
}
fpath, err := exportser.WithdrawOrderExport(data)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}
// GoldTurnover doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/withdrawOrder [get]
func GoldTurnover(ctx *gin.Context) {
req := txnmod.CoinLogReq{}
if err := ctx.ShouldBind(&req); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, nil)
return
}
m := make(map[string]interface{})
if req.UID > 0 {
m["uid"] = req.UID
}
if req.Balance == 1 {
i := make(map[string]interface{})
i["$gt"] = 0
m["amount"] = i
}
if req.Balance == 2 {
i := make(map[string]interface{})
i["$lt"] = 0
m["amount"] = i
}
if !req.End.IsZero() {
i := make(map[string]interface{})
i["$gte"] = req.Start
i["$lt"] = req.End
m["createdAt"] = i
}
if len(req.TranType) != 0 {
m["tranType"] = req.TranType
}
infos, _, _, _ := txnmod.GetCoinLogs(1, 100000, m)
fpath, err := exportser.GoldTurnoverExport(infos)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}
// VideoIncome doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/withdrawOrder [get]
func VideoIncome(ctx *gin.Context) {
type Query struct {
StartTime *time.Time `form:"startTime" json:"startTime" binding:""` //开始时间
EndTime *time.Time `form:"endTime" json:"endTime" binding:""` //结束时间
UID *uint64 `form:"uid" json:"uid" binding:""` //uid
Vid *string `form:"vid" json:"vid" binding:""` //vid
}
var arg struct {
commod.Page
Query
}
if err := ctx.ShouldBind(&arg); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, "stat VideoEarnStats arg error "+err.Error())
return
}
endTime := time.Now()
if arg.EndTime != nil {
endTime = timerange.LocDayRange(*arg.EndTime).Head
}
startTime := time.Time{}
if arg.StartTime != nil {
startTime = timerange.LocDayRange(*arg.StartTime).Head
}
var vid *primitive.ObjectID
if arg.Vid != nil {
id, _ := primitive.ObjectIDFromHex(*arg.Vid)
vid = &id
}
var vids []primitive.ObjectID
if arg.UID != nil {
list, _ := vidmod.FindMany((&vidmod.PublisherIDMatch{PublisherID: arg.UID}).New())
vids = list.IDs()
}
limit := int64(100000)
skip := int64(0)
page, _ := statvidmod.VideoStatPages(skip, limit,
statvidmod.SumDateGTEAndLTMatch{GTE: &startTime, LT: &endTime},
statvidmod.VidMatch{Vid: vid},
statvidmod.VidInMatch{Vids: vids},
)
fpath, err := exportser.VideoIncomeExport(page.List)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}
// OrderStat doc
// @Summary 导出文件
// @Description 导出excel文件
// @Tags Web-Export
// @Accept mpfd,json
// @Produce json,html
// @Param fileType query integer true "导出文件的类型"
// @Success 200 {string} json "{"msg": "操作成功", "data":[]}"
// @Failure 400 {string} json "{"msg": "操作失败"}"
// @Router /web/export/withdrawOrder [get]
func OrderStat(ctx *gin.Context) {
var args struct {
Type *int `form:"type" json:"type"` //订单类型
Channel *string `form:"channel" json:"channel"`
Start *time.Time `form:"start" json:"start"`
End *time.Time `form:"end" json:"end"`
}
if err := ctx.ShouldBind(&args); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, nil)
return
}
endTime := time.Now()
if args.End != nil {
endTime = *args.End
}
var startTime time.Time
if args.Start != nil {
startTime = *args.Start
}
skip := int64(0)
limit := int64(100000)
mats := []statordermod.Matcher{
(&statordermod.ChannelMatch{}).New(),
(&statordermod.TypeMatch{}).New(),
(&statordermod.SumDateGTEAndLTMatch{GTE: &startTime, LT: &endTime}).New(),
}
list, err := statordermod.List(nil, &skip, &limit, mats...)
if err != nil {
common.ServeJSON(ctx, stderr.ErrDbQueryError, "")
return
}
fpath, err := exportser.OrderStatExport(list)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}
// CodeList 导出兑换码
func CodeList(ctx *gin.Context) {
var param = exchcodemod.ListReqParam{}
if err := ctx.ShouldBind(&param); err != nil {
common.ServeJSON(ctx, stderr.ErrParamError, err)
return
}
data := exchcodeser.ExchangeCodes(param)
fpath, err := exportser.ExchangeCodeExport(data)
if err != nil {
log.Warn("request multipart wrong ", log.E(err))
common.ServeJSON(ctx, stderr.ErrExportFileFail, err.Error())
return
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filepath.Base(fpath)))
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(fpath)
}