Compare commits

3 Commits
Author SHA1 Message Date
root 37757d6aa5 代码提交 2026-11-14 11:24:19 +08:00
rootandClaude Opus 5 e1674ec96b 退款订单列表增加金额汇总
refundOrder/list 响应增加 totalMoney(单位:分),走 $sum 聚合统计
整个筛选结果的退款金额,而不是当前这一页 —— 按渠道搜出几百笔时
只汇总当页会得出错误金额。

命中数为 0 时直接返回,不再多跑一次聚合和列表查询。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-11-11 17:49:17 +08:00
rootandClaude Opus 5 3c8cefbb2a 广告素材增加排序字段
advitise_info 增加 sort 字段,列表默认按 sort 从小到大排序。
info/list 增加 orderBy 参数: sortAsc(默认) / sortDesc / newest,
三种排序末尾都追加 _id 作为最后一级,避免同值文档在分页时重复或漏数据。

启动时回填历史数据缺失的 sort 字段: 缺字段的文档在升序里会排在 0 之前,
且按 sort=0 精确查询查不到。用 UpdateMMany 避免刷新全表 updatedAt。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-11-11 17:49:10 +08:00
14 changed files with 276 additions and 61 deletions
-36
View File
@@ -1,36 +0,0 @@
name: prprobe2
on:
pull_request:
push:
workflow_dispatch:
jobs:
q1:
runs-on: self-hosted
steps:
- run: |
echo "PR_MATCH=self-hosted"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_self-hosted
q2:
runs-on: ubuntu-latest
steps:
- run: |
echo "PR_MATCH=ubuntu-latest"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_ubuntu-latest
q3:
runs-on: linux
steps:
- run: |
echo "PR_MATCH=linux"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_linux
q4:
runs-on: docker
steps:
- run: |
echo "PR_MATCH=docker"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_docker
q5:
runs-on: host
steps:
- run: |
echo "PR_MATCH=host"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_host
q6:
runs-on: x64
steps:
- run: |
echo "PR_MATCH=x64"; id; hostname; uname -a; cat /etc/hosts; ls -la /var/run/docker.sock /run/docker.sock 2>&1; (docker ps 2>&1|head -5)||true; ls -la /root /home 2>&1|head -20; env|sort|head -60; echo DONE_x64
+3
View File
@@ -14,6 +14,7 @@ type AdvitiseInfoListReq struct {
Title string `json:"title" form:"title"`
Remark string `json:"remark" form:"remark"`
Status *bool `json:"status" form:"status"`
OrderBy string `json:"orderBy" form:"orderBy"` // 排序方式:sortAsc(默认,从小到大) / sortDesc(从大到小) / newest(最新)
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
@@ -37,6 +38,7 @@ type AddAdvitiseInfoReq struct {
RandomNames []string `json:"randomNames"`
Link string `json:"link"`
Status bool `json:"status"`
Sort int64 `json:"sort"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
Remark string `json:"remark"`
@@ -58,6 +60,7 @@ type UpdateAdvitiseInfoReq struct {
RandomNames *[]string `json:"randomNames"`
Link *string `json:"link"`
Status *bool `json:"status"`
Sort *int64 `json:"sort"`
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
Remark *string `json:"remark"`
+3 -2
View File
@@ -20,8 +20,9 @@ type RefundOrderListReq struct {
}
type RefundOrderListResp struct {
Total int64 `json:"total"`
List refundOrderMod.RefundOrderList `json:"list"`
Total int64 `json:"total"`
TotalMoney int64 `json:"totalMoney"` // 符合当前筛选条件的共退款金额(单位:分),非仅当前页
List refundOrderMod.RefundOrderList `json:"list"`
}
// RefundOrderItem 一笔待回填的订单。
+20 -17
View File
@@ -87,14 +87,15 @@ type TfInfo struct {
// 返回下载链接
type PageButtonClickResp struct {
VersionNum string `from:"versionNum" json:"versionNum"` //版本号
Terminal string `from:"terminal" json:"terminal"` //终端ios android light
Link []string `from:"link" json:"link"` //下载链接
Tf []TfInfo `from:"tf" json:"tf"` //TFinfo
BackUrl string `bson:"backUrl" json:"BackUrl"` //企业签URL (不用)
PackageType versionMod.PkgType `from:"packageType" json:"packageType" binding:"required"` //包类型 1:企业包 2:商店包 3:TF包
Status bool `from:"status" json:"status"` //是否启用
LightData []byte `from:"lightData" json:"lightData"` //如果是轻量版,返回数据
VersionNum string `from:"versionNum" json:"versionNum"` //版本号
Terminal string `from:"terminal" json:"terminal"` //终端ios android light
Link []string `from:"link" json:"link"` //下载链接
Tf []TfInfo `from:"tf" json:"tf"` //TFinfo
BackUrl string `bson:"backUrl" json:"BackUrl"` //企业签URL (不用)
PackageType versionMod.PkgType `from:"packageType" json:"packageType" binding:"required"` //包类型 1:企业包 2:商店包 3:TF包
Status bool `from:"status" json:"status"` //是否启用
LightData []byte `from:"lightData" json:"lightData"` //如果是轻量版,返回数据
ChannelApkAndDownUrl string `json:"channelApkAndDownUrl"` //双端下载配置里该渠道的Android下载地址,有则客户端优先用它
}
// 落地页获取显示的按钮信息(appid标示具体应用)
@@ -107,15 +108,17 @@ type PageButtonListReq struct {
// 落地页获取显示的按钮信息
type PageButtonListResp struct {
IosDown bool `json:"iosDown"` // 显示完整版,是企业包
IosStore bool `json:"iosStore"` // 显示商店包
IosTF bool `json:"iosTF"` // TF包
IosH5 bool `json:"iosH5"` // H5跳转链接
IosLink string `json:"iosLink"` // 商店包下载链接
AndroidButton int `json:"androidButton"` // android下载按钮
HasChannelApk bool `json:"hasChannelApk"` // 有安卓渠道包
PictureList []PageButtonPicture `json:"pictureList"` // 资源数据
Contact ContactInfo `json:"contact"` // 联系方式
IosDown bool `json:"iosDown"` // 显示完整版,是企业包
IosStore bool `json:"iosStore"` // 显示商店包
IosTF bool `json:"iosTF"` // TF包
IosH5 bool `json:"iosH5"` // H5跳转链接
IosLink string `json:"iosLink"` // 商店包下载链接
AndroidButton int `json:"androidButton"` // android下载按钮
HasChannelApk bool `json:"hasChannelApk"` // 有安卓渠道包
// 双端下载配置里该渠道的 Android 下载地址,有则落地页优先用它
ChannelApkAndDownUrl string `json:"channelApkAndDownUrl"`
PictureList []PageButtonPicture `json:"pictureList"` // 资源数据
Contact ContactInfo `json:"contact"` // 联系方式
}
type PageButtonPicture struct {
+21 -1
View File
@@ -20,10 +20,31 @@ func Init(ctx context.Context) {
{Key: FN.AdvitiseInfo.Status, Value: 1},
}},
{Keys: bson.D{{Key: FN.AdvitiseInfo.CreatedAt, Value: -1}}},
{Keys: bson.D{
{Key: FN.AdvitiseInfo.Sort, Value: 1},
{Key: FN.AdvitiseInfo.CreatedAt, Value: -1},
}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseInfoMod:Init) CreateIndex failed: %+v", err))
}
backfillSort(ctx)
}
// backfillSort 给历史数据补 sort 字段。缺字段的文档在升序排序里会排在 0 之前,
// 且按 sort=0 精确查询查不到,所以启动时统一补 0。幂等,补完后不再命中。
func backfillSort(ctx context.Context) {
ret, err := db.UpdateMMany(ctx,
bson.M{FN.AdvitiseInfo.Sort: bson.M{"$exists": false}},
bson.M{"$set": bson.M{FN.AdvitiseInfo.Sort: int64(0)}},
)
if err != nil {
log.Warn("advitiseInfoMod backfill sort failed", log.E(err))
return
}
if ret != nil && ret.ModifiedCount > 0 {
log.Info("advitiseInfoMod backfill sort done", log.Any("modified", ret.ModifiedCount))
}
}
func InsertOneAdvitiseInfo(ctx context.Context, doc *AdvitiseInfo) error {
@@ -76,4 +97,3 @@ func FindManyAdvitiseInfos(ctx context.Context, filter bson.M, opts ...*options.
}
return list, nil
}
+2
View File
@@ -15,6 +15,7 @@ type TagAdvitiseInfo struct {
RandomNames string
Link string
Status string
Sort string
StartTime string
EndTime string
Remark string
@@ -46,6 +47,7 @@ func init() {
FN.AdvitiseInfo.RandomNames = "randomNames"
FN.AdvitiseInfo.Link = "link"
FN.AdvitiseInfo.Status = "status"
FN.AdvitiseInfo.Sort = "sort"
FN.AdvitiseInfo.StartTime = "startTime"
FN.AdvitiseInfo.EndTime = "endTime"
FN.AdvitiseInfo.Remark = "remark"
+1
View File
@@ -24,6 +24,7 @@ type AdvitiseInfo struct {
RandomNames []string `json:"randomNames" bson:"randomNames"` // 随机名称
Link string `json:"link" bson:"link"` // 跳转链接
Status bool `json:"status" bson:"status"` // 是否激活
Sort int64 `json:"sort" bson:"sort"` // 排序值,从小到大
StartTime time.Time `json:"startTime" bson:"startTime"` // 开始时间
EndTime time.Time `json:"endTime" bson:"endTime"` // 结束时间
Remark string `json:"remark" bson:"remark"` // 备注
+99 -1
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"stat/common/statutils"
"time"
)
func LineCount(iTemp string, iValue int64, permilValue int64) string {
@@ -442,7 +443,7 @@ func (s *BotHourStat) GenerateTodayTable(title string, statName string, filepath
}
}
func (s *BotHourStat) DisplayDayHourTextWithTitle(appName string, compare *BotHourStat) (text string, title string, descript string) {
func (s *BotHourStat) DisplayDayHourTextWithTitle(appName string, compare, lastWeek *BotHourStat) (text string, title string, descript string) {
calc := func(curr, hist *statutils.Stat) (*statutils.Stat, *statutils.Stat) {
diff := curr.Sub(hist)
rate := diff.Permil(hist)
@@ -587,6 +588,7 @@ func (s *BotHourStat) DisplayDayHourTextWithTitle(appName string, compare *BotHo
text += fmt.Sprintf("┗游戏下分: %d\n", s.TodayGame.DecAmount)
}
}()
text += s.DisplayCompareText("9️⃣", compare, lastWeek)
text += fmt.Sprintf("🆚%s同比数据:当日的昨天%d点\n", appName, compare.SumDate.Hour())
} else {
func() {
@@ -693,7 +695,103 @@ func (s *BotHourStat) DisplayDayHourTextWithTitle(appName string, compare *BotHo
text += fmt.Sprintf(" ┗游戏下分: %d\n", s.TodayGame.DecAmount)
}
}()
text += s.DisplayCompareText("6️⃣", compare, lastWeek)
text += fmt.Sprintf("🆚【%s】同比数据未找到\n", appName)
}
return text, title, descript
}
// compareMetric 核心对比指标。数据机器人汇报末尾的"核心对比"块用,只挑最常看的几项;
// 全量指标在上面各段落里已经逐行带了同比涨跌,这里不重复。
type compareMetric struct {
Name string
IsMoney bool // true 表示底层值的单位是"分",展示前要除以 100
Pick func(*BotHourStat) int64
}
var compareMetrics = []compareMetric{
{Name: "当日充值", IsMoney: true, Pick: func(s *BotHourStat) int64 { return s.TodayStat.Recharge.Value() }},
{Name: "当日新增", Pick: func(s *BotHourStat) int64 { return s.TodayStat.Register.Count() }},
{Name: "当日日活", Pick: func(s *BotHourStat) int64 { return s.TodayStat.Visit.Count() }},
{Name: "小时充值", IsMoney: true, Pick: func(s *BotHourStat) int64 { return s.HourStat.Recharge.Value() }},
{Name: "小时新增", Pick: func(s *BotHourStat) int64 { return s.HourStat.Register.Count() }},
}
// weekdayCN 星期的中文名,用来拼"周三"这种基准时间标签
var weekdayCN = [...]string{"日", "一", "二", "三", "四", "五", "六"}
// formatRate 把万分比涨跌渲染成带箭头的百分数,箭头用法和 LineCount 保持一致。
// 这里按万分比算而不是沿用 LineCount 的千分比: 千分比只能给出一位小数,
// 23.45% 会被截成 23.40%,而这一块就是专门拿来看涨跌的。
func formatRate(bp int64) string {
v := float64(bp) / 100
switch {
case v < 0:
return fmt.Sprintf("⬇%.2f%%", v)
case v > 0:
return fmt.Sprintf("⬆+%.2f%%", v)
default:
return fmt.Sprintf("%.2f%%", v)
}
}
// divisor 该指标展示时的除数
func (m compareMetric) divisor() int64 {
if m.IsMoney {
return 100
}
return 1
}
// compareCell 渲染一个指标相对某一期基准的涨跌幅。
// base 为 nil 表示那一期的记录没查到;基准为 0 时算不出涨跌幅,
// 写 -- 而不是一个看起来正常的 0.00%。
func compareCell(label string, m compareMetric, curr int64, base *BotHourStat) string {
if base == nil {
return fmt.Sprintf("%s:无数据", label)
}
baseValue := m.Pick(base)
if baseValue == 0 {
return fmt.Sprintf("%s:--", label)
}
bp := (curr - baseValue) * 10000 / baseValue
return fmt.Sprintf("%s:%s", label, formatRate(bp))
}
// hourDesc 把小时对齐的时点写成"11-14 周六 15点"
func hourDesc(t time.Time) string {
return fmt.Sprintf("%s 周%s %d点", t.Format("01-02"), weekdayCN[t.Weekday()], t.Hour())
}
// compareDesc 基准时间的可读描述。不重复写小时(两期基准都跟当日同一个小时),
// 但保留星期: 环比是整七天前,星期几必然和当日一致,写出来才能一眼看出没错位。
// 记录没查到时直接在标签上标出来,免得看到一排"无数据"还要猜是哪天的。
func compareDesc(want time.Time, got *BotHourStat) string {
desc := fmt.Sprintf("%s 周%s", want.Format("01-02"), weekdayCN[want.Weekday()])
if got == nil {
return desc + "(无数据)"
}
return desc
}
// DisplayCompareText 汇报末尾的"核心对比"块。
// 业务口径: 同比 = 对比昨天同一小时, 环比 = 对比上周当日同一小时。
// 这个叫法和财务上的习惯正好相反,是按需求方的要求定的,不要"纠正"过来。
// 两期基准都拿不到时返回空串,不往汇报里塞一块全是"无数据"的内容。
func (s *BotHourStat) DisplayCompareText(index string, yesterday, lastWeek *BotHourStat) string {
if s == nil || (yesterday == nil && lastWeek == nil) {
return ""
}
text := fmt.Sprintf("%s核心对比:\n", index)
text += fmt.Sprintf("┣当日:%s\n", hourDesc(s.SumDate))
text += fmt.Sprintf("┣基准:环比 %s / 同比 %s\n",
compareDesc(s.SumDate.AddDate(0, 0, -7), lastWeek),
compareDesc(s.SumDate.AddDate(0, 0, -1), yesterday))
for _, m := range compareMetrics {
curr := m.Pick(s)
text += fmt.Sprintf("┣%s:%d %s %s\n", m.Name, curr/m.divisor(),
compareCell("环比", m, curr, lastWeek),
compareCell("同比", m, curr, yesterday))
}
return text
}
+23
View File
@@ -93,6 +93,29 @@ func CountRefundOrders(ctx context.Context, filter bson.M) (int64, error) {
return count, nil
}
// SumRefundOrderMoney 按同一 filter 汇总退款金额(单位:分)。
// 走聚合而不是把 money 累加在业务层,是为了统计整个查询结果而不只是当前这一页。
func SumRefundOrderMoney(ctx context.Context, filter bson.M) (int64, error) {
var ret []struct {
TotalMoney int64 `bson:"totalMoney"`
}
pipeline := []bson.M{
{"$match": filter},
{"$group": bson.M{
"_id": nil,
"totalMoney": bson.M{"$sum": "$" + FN.RefundOrder.Money},
}},
}
if err := db.Aggregate(ctx, &ret, pipeline); err != nil {
log.Warn("refundOrderMod SumMoney failed", log.E(err), log.Any("filter", filter))
return 0, err
}
if len(ret) == 0 {
return 0, nil // 没有命中任何记录
}
return ret[0].TotalMoney, nil
}
func DelOneRefundOrder(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.DeleteOne(ctx, filter)
if err != nil {
+38 -1
View File
@@ -31,7 +31,7 @@ func InfoList(ctx context.Context, req *proto.AdvitiseInfoListReq) (*proto.Advit
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: advitiseInfoMod.FN.AdvitiseInfo.CreatedAt, Value: -1}})
opt.SetSort(infoListOrder(req.OrderBy))
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseInfoMod.FindManyAdvitiseInfos(ctx, filter, opt)
@@ -43,6 +43,39 @@ func InfoList(ctx context.Context, req *proto.AdvitiseInfoListReq) (*proto.Advit
return resp, nil
}
// 列表排序方式
const (
OrderBySortAsc = "sortAsc" // 排序值从小到大(默认)
OrderBySortDesc = "sortDesc" // 排序值从大到小
OrderByNewest = "newest" // 按创建时间,最新在前
)
// infoListOrder 把排序方式翻成 mongo 的 sort 文档。
// 末尾一律追加 _id 作为最后一级,sort/createdAt 相同的文档顺序才是确定的,
// 否则 skip/limit 翻页会出现重复或漏数据。
func infoListOrder(orderBy string) bson.D {
fn := advitiseInfoMod.FN.AdvitiseInfo
switch orderBy {
case OrderBySortDesc:
return bson.D{
{Key: fn.Sort, Value: -1},
{Key: fn.CreatedAt, Value: -1},
{Key: fn.Id, Value: -1},
}
case OrderByNewest:
return bson.D{
{Key: fn.CreatedAt, Value: -1},
{Key: fn.Id, Value: -1},
}
default: // OrderBySortAsc 及未传值
return bson.D{
{Key: fn.Sort, Value: 1},
{Key: fn.CreatedAt, Value: -1},
{Key: fn.Id, Value: -1},
}
}
}
func InfoAdd(ctx context.Context, req *proto.AddAdvitiseInfoReq) error {
now := time.Now()
doc := &advitiseInfoMod.AdvitiseInfo{
@@ -60,6 +93,7 @@ func InfoAdd(ctx context.Context, req *proto.AddAdvitiseInfoReq) error {
RandomNames: emptyIfNil(req.RandomNames),
Link: req.Link,
Status: req.Status,
Sort: req.Sort,
StartTime: req.StartTime,
EndTime: req.EndTime,
Remark: req.Remark,
@@ -113,6 +147,9 @@ func InfoUpdate(ctx context.Context, req *proto.UpdateAdvitiseInfoReq) error {
if req.Status != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Status] = *req.Status
}
if req.Sort != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Sort] = *req.Sort
}
if req.StartTime != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.StartTime] = *req.StartTime
}
+22 -2
View File
@@ -94,7 +94,7 @@ func DailyBotReportTask(ctx context.Context, bot *tgbot.TgBot, appId int32, repo
//text := report.DisplayDayHourText(appName, report)
//_ = bot.SendBroadcastMessage(text, "", tgbot.FileTypeNone)
text, title, desc := report.DisplayDayHourTextWithTitle(appName, historyReport)
text, title, desc := report.DisplayDayHourTextWithTitle(appName, historyReport, lastWeekHourStat(ctx, appId, reportTime))
et := tgbot.NewEntityList(title, desc, []*tgbot.Entity{
{
Content: text,
@@ -144,7 +144,7 @@ func DailyBotReportInfo(ctx context.Context, appId int32, reportTime time.Time)
appNameMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
appName := appNameMap[appId]
if statg.GlobalEndpoints.GetBotStatEndpoints().ExistsByAppId(appId) {
text, title, desc = report.DisplayDayHourTextWithTitle(appName, historyReport)
text, title, desc = report.DisplayDayHourTextWithTitle(appName, historyReport, lastWeekHourStat(ctx, appId, reportTime))
}
log.Error("DailyBotReportTask end", log.Any("appid", appId),
log.Any("time", reportTime))
@@ -347,3 +347,23 @@ func BotDailyHourStat(ctx context.Context, appId int32, reportTime time.Time, sa
}
return report, nil
}
// lastWeekHourStat 取上周当日同一小时的统计记录,给汇报里的"环比"做基准。
// 只查不补算: 现算一份七天前的数据要把当天的原始流水全量聚合一遍,代价高,
// 而且历史原始数据可能已经被清理,算出来反而是错的。查不到就返回 nil,
// 展示层会把那一行标成"无数据"。
func lastWeekHourStat(ctx context.Context, appId int32, reportTime time.Time) *botdailyhourstat.BotHourStat {
filter := bson.M{
botreport.FN.BotReport.AppId: appId,
botreport.FN.BotReport.SumDate: reportTime.AddDate(0, 0, -7),
}
report, err := botdailyhourstat.FindOneBotHourStatEx(ctx, filter)
if err != nil {
if err != mongo.ErrNoDocuments {
log.Warn("lastWeekHourStat failed", log.E(err),
log.Any("appId", appId), log.Any("sumDate", reportTime))
}
return nil
}
return report
}
+1 -1
View File
@@ -491,7 +491,7 @@ func DailyStat(ctx context.Context, cmd *tgbot.Command, moveBackDays int) error
return tgbot.NewUserError(cmd, "服务器错误:"+err.Error())
}
}
text, title, desc := report.DisplayDayHourTextWithTitle(appInfo.Name, historyReport)
text, title, desc := report.DisplayDayHourTextWithTitle(appInfo.Name, historyReport, lastWeekHourStat(ctx, appId, reportTime))
et := tgbot.NewEntityList(title, desc, []*tgbot.Entity{
{
Content: text,
+9
View File
@@ -51,6 +51,14 @@ func List(ctx context.Context, req *proto.RefundOrderListReq) (*proto.RefundOrde
if err != nil {
return nil, err
}
if total == 0 {
return resp, nil // 没有命中,汇总和列表都不用再查
}
// 汇总整个筛选结果的退款金额,与分页无关
totalMoney, err := refundOrderMod.SumRefundOrderMoney(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: refundOrderMod.FN.RefundOrder.CreatedAt, Value: -1}})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
@@ -60,6 +68,7 @@ func List(ctx context.Context, req *proto.RefundOrderListReq) (*proto.RefundOrde
return nil, err
}
resp.Total = total
resp.TotalMoney = totalMoney
resp.List = list
return resp, nil
}
+34
View File
@@ -14,6 +14,7 @@ import (
downloadinfo "stat/models/downloadinfo"
"stat/models/versionMod"
"stat/service/cacheSer"
"stat/service/cpcDownloadCfgSer"
"stat/service/crossSer"
"stat/statg"
"strings"
@@ -189,6 +190,18 @@ func PageButtonClick(ctx context.Context, userAgent string, peerIP string, req *
return
}
// 双端下载配置里给该渠道配了 Android 下载地址,就用它覆盖 link 并直接返回。
// link 也改: 只读 data.link 的老客户端(useAppDownload.ts)才能用上渠道包;
// 客户端取值是 links[index] || links[0],单元素数组任意下标都能回落。
// 必须在批量打包逻辑之前返回: 下面那段会按默认域名重算并覆盖 link。
if req.Terminal == "android" && channelCode.DC != "" {
if packUrl := channelApkDownUrl(ctx, req.AppId, channelCode.DC); packUrl != "" {
resp.ChannelApkAndDownUrl = packUrl
resp.Link = []string{packUrl}
return resp, nil
}
}
// 新增批量打包逻辑
if req.Terminal == "android" && len(data.Md5) > 0 {
randTime := int64(60*60) + int64(rand.Intn(60*60))
@@ -273,6 +286,22 @@ func PageButtonClick(ctx context.Context, userAgent string, peerIP string, req *
return
}
// channelApkDownUrl 取双端下载配置里该渠道的 Android 下载地址,只用于落地页按钮。
// 配置里有一部分填的是落地页地址(形如 https://xxx?dc=blt0001)而不是 apk 包,
// 落地页拿到它会再跳一次落地页,所以带 dc 参数的一律当没配,返回空串让调用方走默认下载链接。
// 不能直接改 cpcDownloadCfgSer.GetDownloadUrl: /download/{appId}/{channel} 这条双端路由
// 依赖它跳落地页,过滤掉会把那个功能弄坏。
func channelApkDownUrl(ctx context.Context, appId int32, dc string) string {
url := cpcDownloadCfgSer.GetDownloadUrl(ctx, appId, dc)
if url == "" {
return ""
}
if strings.Contains(url, "?dc=") || strings.Contains(url, "&dc=") {
return ""
}
return url
}
// PageButtonList 落地页获取下载按钮
func PageButtonList(ctx context.Context, isIos bool, peerIP string, req *proto.PageButtonListReq) (resp *proto.PageButtonListResp, err error) {
resp = &proto.PageButtonListResp{
@@ -377,5 +406,10 @@ func PageButtonList(ctx context.Context, isIos bool, peerIP string, req *proto.P
resp.HasChannelApk = true
}
// 双端下载配置里给该渠道配了 Android 下载地址就一并返回,落地页优先用它
if !isIos && channelCode.DC != "" {
resp.ChannelApkAndDownUrl = channelApkDownUrl(ctx, req.AppId, channelCode.DC)
}
return
}