Merge origin/zuncle into main

This commit is contained in:
win 2026-03-17 16:00:23 +08:00
commit a450a92673
7 changed files with 301 additions and 15 deletions

View File

@ -0,0 +1,135 @@
## 实施计划GMV 支付方式拆分展示
### 需求分析
当前渠道统计只展示一个"累计实付金额"(GMV 总数),用户无法看到这笔钱的构成。需要拆分为:
- **现金支付** (`actual_amount`) — 用户通过微信支付的真金白银
- **优惠券抵扣** (`discount_amount`) — 优惠券抵扣部分
- **积分抵扣** (`points_amount`) — 积分抵扣部分当前数据为0但字段已预留
验证:`total_amount = actual_amount + discount_amount + points_amount` 在所有订单上完全成立0条不等式
### 数据现状dev 环境)
| 支付方式 | 订单数 | 金额(元) | 占比 |
|---------|--------|---------|------|
| GMV 总额 | 3,595 | 124,526.80 | 100% |
| 现金 | 3,595 | 90,067.85 | 72.3% |
| 优惠券 | 1,896 | 34,458.95 | 27.7% |
| 积分 | 0 | 0.00 | 0% |
### 技术方案
orders 表已有完整的拆分字段,**无需新建表或字段**,只需在查询和展示层增加维度。
### 实施步骤
#### Step 1: 后端 — 扩展数据结构
文件:`internal/service/channel/channel.go`
1.1 `StatsOverview` 结构体新增字段:
```go
CashCents int64 `json:"cash_cents"` // 现金支付(分)
CouponCents int64 `json:"coupon_cents"` // 优惠券抵扣(分)
PointsCents int64 `json:"points_cents"` // 积分抵扣(分)
```
1.2 `StatsDailyItem` 结构体新增字段:
```go
CashCents int64 `json:"cash_cents"`
CouponCents int64 `json:"coupon_cents"`
PointsCents int64 `json:"points_cents"`
```
#### Step 2: 后端 — 修改 GMV 查询方法
文件:`internal/service/channel/channel.go`
2.1 `calcGMVByTotalAmount` 改为同时返回 actual_amount / discount_amount / points_amount 的分组统计:
```go
type GMVBreakdown struct {
Total int64
Cash int64
Coupon int64
Points int64
}
func calcGMVByTotalAmount(...) (GMVBreakdown, map[string]GMVBreakdown)
```
查询 SELECT 增加:`orders.actual_amount, orders.discount_amount, orders.points_amount`
2.2 `GetStats` 方法中将拆分数据写入 Overview 和 Daily
```go
out.Overview.CashCents = breakdown.Cash
out.Overview.CouponCents = breakdown.Coupon
out.Overview.PointsCents = breakdown.Points
```
#### Step 3: 后端 — List 接口也返回拆分数据(可选)
文件:`internal/service/channel/channel.go`
`ChannelWithStat` 结构体和 `List` 方法中的 GMV 查询也增加拆分统计,用于渠道列表页 tooltip 展示。
#### Step 4: 前端 — API 类型更新
文件:`web/admin/src/api/channels.ts`
```ts
interface StatsOverview {
// ... existing fields
cash_cents?: number
coupon_cents?: number
points_cents?: number
}
interface StatsDailyItem {
// ... existing fields
cash_cents?: number
coupon_cents?: number
points_cents?: number
}
```
#### Step 5: 前端 — Stats 概览卡片展示
文件:`web/admin/src/views/operations/channels/index.vue`
在"累计实付金额"卡片下方或旁边展示支付构成:
- 显示 3 个子指标:现金 / 优惠券 / 积分
- 各自显示金额和占比百分比
- 积分为 0 时可隐藏或灰显
#### Step 6: 前端 — 每日趋势图支持
文件:`web/admin/src/views/operations/channels/index.vue`
在 revenue tab 的折线图中,可以选择查看:
- GMV 总额(默认)
- 现金 / 优惠券 / 积分 分层堆叠
#### Step 7: 测试
文件:`internal/service/channel/channel_stats_test.go`
更新测试用例,验证 GMV 拆分字段的正确性。
### 关键文件
| 文件 | 操作 | 说明 |
|------|------|------|
| `internal/service/channel/channel.go` | 修改 | 数据结构 + 查询逻辑 |
| `internal/service/channel/channel_stats_test.go` | 修改 | 测试覆盖拆分字段 |
| `web/admin/src/api/channels.ts` | 修改 | API 类型定义 |
| `web/admin/src/views/operations/channels/index.vue` | 修改 | 概览卡片 + 图表展示 |
### 风险与缓解
| 风险 | 缓解措施 |
|------|----------|
| 旧版前端未适配新字段 | 新字段均为可选,不影响旧版展示 |
| 积分字段当前全为0 | 字段预留,后续开启积分抵扣时自动生效 |
| 查询性能 | 无额外 JOIN只增加 3 个 SUM 列,影响可忽略 |

View File

@ -0,0 +1,147 @@
//go:build ignore
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:bindbox2025kdy@tcp(150.158.78.154:3306)/dev_game?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("连接失败:", err)
return
}
filter := "users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type IN (2,3,4) AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)"
// 1. 各字段使用分布
type FieldStats struct {
Label string
Count int64
Sum int64
}
fmt.Println("========== GMV 支付方式拆分数据探查 ==========")
fmt.Println()
// actual_amount (现金)
var cash FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'现金(actual_amount)' as label, COUNT(*) as count, COALESCE(SUM(actual_amount),0) as sum").
Where(filter).Scan(&cash)
// discount_amount (优惠券)
var coupon FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'优惠券(discount_amount)' as label, COUNT(*) as count, COALESCE(SUM(discount_amount),0) as sum").
Where(filter + " AND discount_amount > 0").Scan(&coupon)
// points_amount (积分)
var points FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'积分(points_amount)' as label, COUNT(*) as count, COALESCE(SUM(points_amount),0) as sum").
Where(filter + " AND points_amount > 0").Scan(&points)
// 道具卡 (item_card_id > 0)
var itemCard FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'道具卡(item_card_id)' as label, COUNT(*) as count, 0 as sum").
Where(filter + " AND item_card_id > 0").Scan(&itemCard)
// 总 GMV
var totalGMV FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'总GMV(total_amount)' as label, COUNT(*) as count, COALESCE(SUM(total_amount),0) as sum").
Where(filter).Scan(&totalGMV)
fmt.Printf("%-25s %8s %14s\n", "字段", "订单数", "金额(元)")
fmt.Println("--------------------------------------------------")
for _, f := range []FieldStats{totalGMV, cash, coupon, points, itemCard} {
fmt.Printf("%-25s %8d %14.2f\n", f.Label, f.Count, float64(f.Sum)/100)
}
// 2. 验证: total = actual + discount + points ?
fmt.Println()
fmt.Println("========== 验证: total_amount = actual + discount + points ? ==========")
type MismatchRow struct {
Count int64
}
var mismatch MismatchRow
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("COUNT(*) as count").
Where(filter + " AND total_amount != (actual_amount + discount_amount + points_amount)").
Scan(&mismatch)
fmt.Printf(" 不等式成立的订单数: %d\n", mismatch.Count)
if mismatch.Count > 0 {
type DetailRow struct {
ID int64
TotalAmount int64
ActualAmount int64
DiscountAmount int64
PointsAmount int64
Diff int64
}
var details []DetailRow
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("orders.id, orders.total_amount, orders.actual_amount, orders.discount_amount, orders.points_amount, (orders.total_amount - orders.actual_amount - orders.discount_amount - orders.points_amount) as diff").
Where(filter + " AND total_amount != (actual_amount + discount_amount + points_amount)").
Limit(5).Scan(&details)
fmt.Println(" 抽样:")
for _, d := range details {
fmt.Printf(" #%d total=%d actual=%d discount=%d points=%d diff=%d\n",
d.ID, d.TotalAmount, d.ActualAmount, d.DiscountAmount, d.PointsAmount, d.Diff)
}
}
// 3. 次卡购买订单的支付方式拆分
fmt.Println()
fmt.Println("========== source_type=4 购买次卡的支付方式 ==========")
var gpCash, gpCoupon, gpPoints FieldStats
gpFilter := "users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type = 4 AND orders.order_no LIKE 'GP%' AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)"
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'现金' as label, COUNT(*) as count, COALESCE(SUM(actual_amount),0) as sum").
Where(gpFilter).Scan(&gpCash)
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'优惠券' as label, COUNT(*) as count, COALESCE(SUM(discount_amount),0) as sum").
Where(gpFilter + " AND discount_amount > 0").Scan(&gpCoupon)
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'积分' as label, COUNT(*) as count, COALESCE(SUM(points_amount),0) as sum").
Where(gpFilter + " AND points_amount > 0").Scan(&gpPoints)
fmt.Printf(" 现金: %d单 %.2f元\n", gpCash.Count, float64(gpCash.Sum)/100)
fmt.Printf(" 优惠券: %d单 %.2f元\n", gpCoupon.Count, float64(gpCoupon.Sum)/100)
fmt.Printf(" 积分: %d单 %.2f元\n", gpPoints.Count, float64(gpPoints.Sum)/100)
// 4. 按 source_type 拆分 GMV 构成
fmt.Println()
fmt.Println("========== 按游戏类型 × 支付方式 ==========")
srcNames := map[int]string{2: "小程序抽奖", 3: "对对碰", 4: "一番赏/次卡"}
for _, st := range []int{2, 3, 4} {
stFilter := fmt.Sprintf("users.deleted_at IS NULL AND orders.status = 2 AND orders.total_amount > 0 AND orders.actual_amount > 0 AND orders.source_type = %d AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)", st)
var total, actual, discount, pts FieldStats
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'total' as label, COUNT(*) as count, COALESCE(SUM(total_amount),0) as sum").
Where(stFilter).Scan(&total)
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'actual' as label, COUNT(*) as count, COALESCE(SUM(actual_amount),0) as sum").
Where(stFilter).Scan(&actual)
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'discount' as label, COUNT(*) as count, COALESCE(SUM(discount_amount),0) as sum").
Where(stFilter).Scan(&discount)
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id").
Select("'points' as label, COUNT(*) as count, COALESCE(SUM(points_amount),0) as sum").
Where(stFilter).Scan(&pts)
fmt.Printf(" %s(type=%d): %d单 GMV=%.2f 现金=%.2f 优惠券=%.2f 积分=%.2f\n",
srcNames[st], st, total.Count,
float64(total.Sum)/100, float64(actual.Sum)/100,
float64(discount.Sum)/100, float64(pts.Sum)/100)
}
}

View File

@ -64,7 +64,7 @@ func main() {
env.Active() // 初始化 env flag依赖已有的全局 -env/ACTIVE_ENV 配置)
configs.Init()
cookie := "passport_csrf_token=40ba4a1be914a9f167320ed28b8c93d7; passport_csrf_token_default=40ba4a1be914a9f167320ed28b8c93d7; is_staff_user=false; s_v_web_id=verify_mkf83bbo_zfQ3q1Gp_5irf_4OOI_9y4N_C253269yUIJy; SHOP_ID=156231010; PIGEON_CID=4339134776748827; __security_mc_1_s_sdk_crypt_sdk=db47f387-4d0b-bf21; bd_ticket_guard_client_web_domain=2; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCTHVTREdkVFRHWUdNMVY3ZDZKS2M4V2FwWGJ1K3JVYmVqRThONTZoeTI4SUJXdmVxZjBLMS9GczE0dWx5RTVRd2d4cjdnaDd6SXdMZjlsWDkwOFZQQWs9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D; bd_ticket_guard_web_domain=3; gfkadpd=4272,23756; ecom_gray_shop_id=156231010; zsgw_business_data=%7B%22uuid%22%3A%226756720f-c380-4bda-ab81-3dd27ca08a2d%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.baidu.069%22%7D; source=seo.baidu.069; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1771350555,1772107597,1772794481,1773223394; HMACCOUNT=9C6B7571794A6624; csrf_session_id=8173f094b830570b2b64e98900924731; passport_mfa_token=CjcMUe8O6Zz52W9O1T3zlEkIxpWSHBCB4dHw9XBdiDU%2BIPU1pzwEXLpVjGth2W2nXGHC8OM6ffSmGkoKPAAAAAAAAAAAAABQK6uUDAbmPNiLgEkCaMWLdiWMpTEiK%2Fm1NGLpqOUmR4vBZtoNbJWrAhzjfim%2BBtfMlxCj6IsOGPax0WwgAiIBA8pTDDU%3D; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1773224382; ttwid=1%7CNnXcElGkMBE8UTpDOFYR5OfCUYkFjQaLyn1EagPBZgM%7C1773224307%7C18bc27eb78d0a5da332f8c3ec951f81229670377d82025fcb5e600e3766e367b; tt_scid=uSkT0B7AzW.AKqYpEsRrpTqtws.7fqp2P4-gBF1FyffuNMOl1AKuRvuymbUWzXRvcc00; odin_tt=6edadb78040b4604bed517fc3edef437495387c8a3bf60fa177788ff81dd88daaed661705eb0729801e665c086b098b263c3090fef72c26e872d2f3172f6e364; passport_auth_status=581a8676e64d918c69ee3930f4dacf8b%2C4bb14205ac4179b872cba76a97208a7e; passport_auth_status_ss=581a8676e64d918c69ee3930f4dacf8b%2C4bb14205ac4179b872cba76a97208a7e; bd_ticket_guard_server_data=eyJ0aWNrZXQiOiJoYXNoLk1SWGtrczRwYTZpWG91ODhuZENOT05idm9iSjI2SHlXOXRYN2JKNTdZMWM9IiwidHNfc2lnbiI6InRzLjIuMDg1MDhmMjljNWI2MjkzMjQ4ZTAwNGY0YjdiNjMwODI4ODk1YjFkZWQ1ZTRlYmFiZTc3NmYzZTUxYWJjZjZhNGM0ZmJlODdkMjMxOWNmMDUzMTg2MjRjZWRhMTQ5MTFjYTQwNmRlZGJlYmVkZGIyZTMwZmNlOGQ0ZmEwMjU3NWQiLCJjbGllbnRfY2VydCI6InB1Yi5CTHVTREdkVFRHWUdNMVY3ZDZKS2M4V2FwWGJ1K3JVYmVqRThONTZoeTI4SUJXdmVxZjBLMS9GczE0dWx5RTVRd2d4cjdnaDd6SXdMZjlsWDkwOFZQQWs9IiwibG9nX2lkIjoiMjAyNjAzMTExODE4NDBGQUVGNkZGMDBCMkUwQTJEQTU2QSIsImNyZWF0ZV90aW1lIjoxNzczMjI0MzIwfQ%3D%3D; uid_tt=e8ca5ad2e6032b72a0fd8c0843ff5e9b; uid_tt_ss=e8ca5ad2e6032b72a0fd8c0843ff5e9b; sid_tt=c1a29f1f0f71ea4ed9fbcde60bc2b390; sessionid=c1a29f1f0f71ea4ed9fbcde60bc2b390; sessionid_ss=c1a29f1f0f71ea4ed9fbcde60bc2b390; PHPSESSID=05ca4c3439dacd9ac5f1d86a78516abb; PHPSESSID_SS=05ca4c3439dacd9ac5f1d86a78516abb; ucas_c0=CkEKBTEuMC4wELaIgqLTr9DYaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0CCg8XNBkiCt4HQBlC_vL6Ekt3t1GdYbhIUI1wJXqAsE71YWUNwS6OvJ9dOEVE; ucas_c0_ss=CkEKBTEuMC4wELaIgqLTr9DYaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0CCg8XNBkiCt4HQBlC_vL6Ekt3t1GdYbhIUI1wJXqAsE71YWUNwS6OvJ9dOEVE; sid_guard=c1a29f1f0f71ea4ed9fbcde60bc2b390%7C1773224328%7C5184000%7CSun%2C+10-May-2026+10%3A18%3A48+GMT; sid_ucp_v1=1.0.0-KDA3MGQyMjJkNmQ1NDUxOGQ1MWRhYTFjMzBkZTZkMDBlMTNlYWJhYWUKGwib1oDYuM3aBxCIg8XNBhiwISAMOAZA9AdIBBoCaGwiIGMxYTI5ZjFmMGY3MWVhNGVkOWZiY2RlNjBiYzJiMzkw; ssid_ucp_v1=1.0.0-KDA3MGQyMjJkNmQ1NDUxOGQ1MWRhYTFjMzBkZTZkMDBlMTNlYWJhYWUKGwib1oDYuM3aBxCIg8XNBhiwISAMOAZA9AdIBBoCaGwiIGMxYTI5ZjFmMGY3MWVhNGVkOWZiY2RlNjBiYzJiMzkw; session_tlb_tag=sttt%7C17%7CwaKfHw9x6k7Z-83mC8KzkP________-tSxexYwusSRjOrIMuB3YiA6EaLnfr1fbbR8LfwAsAu74%3D; BUYIN_SASID=SID2_7615938059562205474; COMPASS_LUOPAN_DT=session_7615939876688511241"
cookie := "passport_csrf_token=0b67ab6212a41bd1903f03d4f9a887f9; passport_csrf_token_default=0b67ab6212a41bd1903f03d4f9a887f9; is_staff_user=false; s_v_web_id=verify_mkf83bbo_zfQ3q1Gp_5irf_4OOI_9y4N_C253269yUIJy; SHOP_ID=156231010; PIGEON_CID=4339134776748827; __security_mc_1_s_sdk_crypt_sdk=db47f387-4d0b-bf21; bd_ticket_guard_client_web_domain=2; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCTHVTREdkVFRHWUdNMVY3ZDZKS2M4V2FwWGJ1K3JVYmVqRThONTZoeTI4SUJXdmVxZjBLMS9GczE0dWx5RTVRd2d4cjdnaDd6SXdMZjlsWDkwOFZQQWs9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D; bd_ticket_guard_web_domain=3; gfkadpd=4272,23756; ecom_gray_shop_id=156231010; zsgw_business_data=%7B%22uuid%22%3A%226756720f-c380-4bda-ab81-3dd27ca08a2d%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.baidu.069%22%7D; source=seo.baidu.069; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1771350555,1772107597,1772794481,1773223394; HMACCOUNT=9C6B7571794A6624; csrf_session_id=8173f094b830570b2b64e98900924731; passport_mfa_token=CjcMUe8O6Zz52W9O1T3zlEkIxpWSHBCB4dHw9XBdiDU%2BIPU1pzwEXLpVjGth2W2nXGHC8OM6ffSmGkoKPAAAAAAAAAAAAABQK6uUDAbmPNiLgEkCaMWLdiWMpTEiK%2Fm1NGLpqOUmR4vBZtoNbJWrAhzjfim%2BBtfMlxCj6IsOGPax0WwgAiIBA8pTDDU%3D; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1773224382; ttwid=1%7CNnXcElGkMBE8UTpDOFYR5OfCUYkFjQaLyn1EagPBZgM%7C1773224307%7C18bc27eb78d0a5da332f8c3ec951f81229670377d82025fcb5e600e3766e367b; tt_scid=uSkT0B7AzW.AKqYpEsRrpTqtws.7fqp2P4-gBF1FyffuNMOl1AKuRvuymbUWzXRvcc00; odin_tt=6edadb78040b4604bed517fc3edef437495387c8a3bf60fa177788ff81dd88daaed661705eb0729801e665c086b098b263c3090fef72c26e872d2f3172f6e364; passport_auth_status=581a8676e64d918c69ee3930f4dacf8b%2C4bb14205ac4179b872cba76a97208a7e; passport_auth_status_ss=581a8676e64d918c69ee3930f4dacf8b%2C4bb14205ac4179b872cba76a97208a7e; bd_ticket_guard_server_data=eyJ0aWNrZXQiOiJoYXNoLk1SWGtrczRwYTZpWG91ODhuZENOT05idm9iSjI2SHlXOXRYN2JKNTdZMWM9IiwidHNfc2lnbiI6InRzLjIuMDg1MDhmMjljNWI2MjkzMjQ4ZTAwNGY0YjdiNjMwODI4ODk1YjFkZWQ1ZTRlYmFiZTc3NmYzZTUxYWJjZjZhNGM0ZmJlODdkMjMxOWNmMDUzMTg2MjRjZWRhMTQ5MTFjYTQwNmRlZGJlYmVkZGIyZTMwZmNlOGQ0ZmEwMjU3NWQiLCJjbGllbnRfY2VydCI6InB1Yi5CTHVTREdkVFRHWUdNMVY3ZDZKS2M4V2FwWGJ1K3JVYmVqRThONTZoeTI4SUJXdmVxZjBLMS9GczE0dWx5RTVRd2d4cjdnaDd6SXdMZjlsWDkwOFZQQWs9IiwibG9nX2lkIjoiMjAyNjAzMTExODE4NDBGQUVGNkZGMDBCMkUwQTJEQTU2QSIsImNyZWF0ZV90aW1lIjoxNzczMjI0MzIwfQ%3D%3D; uid_tt=e8ca5ad2e6032b72a0fd8c0843ff5e9b; uid_tt_ss=e8ca5ad2e6032b72a0fd8c0843ff5e9b; sid_tt=c1a29f1f0f71ea4ed9fbcde60bc2b390; sessionid=c1a29f1f0f71ea4ed9fbcde60bc2b390; sessionid_ss=c1a29f1f0f71ea4ed9fbcde60bc2b390; PHPSESSID=05ca4c3439dacd9ac5f1d86a78516abb; PHPSESSID_SS=05ca4c3439dacd9ac5f1d86a78516abb; ucas_c0=CkEKBTEuMC4wELaIgqLTr9DYaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0CCg8XNBkiCt4HQBlC_vL6Ekt3t1GdYbhIUI1wJXqAsE71YWUNwS6OvJ9dOEVE; ucas_c0_ss=CkEKBTEuMC4wELaIgqLTr9DYaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0CCg8XNBkiCt4HQBlC_vL6Ekt3t1GdYbhIUI1wJXqAsE71YWUNwS6OvJ9dOEVE; sid_guard=c1a29f1f0f71ea4ed9fbcde60bc2b390%7C1773224328%7C5184000%7CSun%2C+10-May-2026+10%3A18%3A48+GMT; sid_ucp_v1=1.0.0-KDA3MGQyMjJkNmQ1NDUxOGQ1MWRhYTFjMzBkZTZkMDBlMTNlYWJhYWUKGwib1oDYuM3aBxCIg8XNBhiwISAMOAZA9AdIBBoCaGwiIGMxYTI5ZjFmMGY3MWVhNGVkOWZiY2RlNjBiYzJiMzkw; ssid_ucp_v1=1.0.0-KDA3MGQyMjJkNmQ1NDUxOGQ1MWRhYTFjMzBkZTZkMDBlMTNlYWJhYWUKGwib1oDYuM3aBxCIg8XNBhiwISAMOAZA9AdIBBoCaGwiIGMxYTI5ZjFmMGY3MWVhNGVkOWZiY2RlNjBiYzJiMzkw; session_tlb_tag=sttt%7C17%7CwaKfHw9x6k7Z-83mC8KzkP________-tSxexYwusSRjOrIMuB3YiA6EaLnfr1fbbR8LfwAsAu74%3D; BUYIN_SASID=SID2_7615938059562205474; COMPASS_LUOPAN_DT=session_7615939876688511241"
if cookie == "" {
fmt.Println("请通过环境变量 DOUYIN_COOKIE 提供抖店 Cookie")
os.Exit(1)

View File

@ -45,16 +45,20 @@ func (h *handler) SubmitAddressShare() core.HandlerFunc {
return
}
// 尝试获取登录用户信息 (可选)
// 登录态验证 - 必须登录才能提交(确保地址归属正确)
var submitUserID *int64
authHeader := ctx.GetHeader("Authorization")
if authHeader != "" {
// 如果有 Authorization 尝试解析
if claims, err := jwtoken.New(configs.Get().JWT.PatientSecret).Parse(authHeader); err == nil {
uid := int64(claims.SessionUserInfo.Id)
submitUserID = &uid
}
if authHeader == "" {
ctx.AbortWithError(core.Error(http.StatusUnauthorized, 10027, "请先登录后再提交收货地址"))
return
}
claims, claimsErr := jwtoken.New(configs.Get().JWT.PatientSecret).Parse(authHeader)
if claimsErr != nil {
ctx.AbortWithError(core.Error(http.StatusUnauthorized, 10027, "登录已过期,请重新登录"))
return
}
uid := int64(claims.SessionUserInfo.Id)
submitUserID = &uid
ip := ctx.Request().RemoteAddr
// 统一使用 ctx.RequestContext() 包含 context 内容

View File

@ -533,7 +533,7 @@ func (s *service) fetchDouyinOrdersByBuyer(cookie string, buyer string, proxy st
params.Set("appid", "1")
params.Set("_bid", "ffa_order")
params.Set("aid", "4272")
params.Set("__token", "a75db7d1f0eb7c205962656dMPidWeczHqu1PZ0Tes1wGdbAcooEJkdvUmpkpo27xfzAYfVbY1iwZHHpzmpDuk7Sa4/9nyfohqJZPBvhI3m8eGGxoi-OUAsPQ/d3EaLX60Uo2gQfqrKNloO9CAeNQ/Y8TrBcd-RhSxKIQCEqNp1uun6pzhR5")
params.Set("__token", "0b67ab6212a41bd1903f03d4f9a887f9")
return s.fetchDouyinOrders(cookie, params, proxy)
}
@ -1004,7 +1004,7 @@ func (s *service) SyncAllOrders(ctx context.Context, duration time.Duration, use
// 临时:强制使用用户提供的最新 Cookie
if len(cfg.Cookie) < 100 {
cfg.Cookie = "passport_csrf_token=afcc4debfeacce6454979bb9465999dc; passport_csrf_token_default=afcc4debfeacce6454979bb9465999dc; is_staff_user=false; zsgw_business_data=%7B%22uuid%22%3A%22fa769974-ba17-4daf-94cb-3162ba299c40%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.fxg.jinritemai.com%22%7D; s_v_web_id=verify_mjqlw6yx_mNQjOEnB_oXBo_4Etb_AVQ9_7tQGH9WORNRy; SHOP_ID=47668214; PIGEON_CID=3501298428676440; x-web-secsdk-uid=663d5a20-e75c-4789-bc98-839744bf70bc; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1766891015,1766979339,1767628404,1768381245; HMACCOUNT=95F3EBE1C47ED196; ttcid=7962a054674f4dd7bf895af73ae3f34142; passport_mfa_token=CjfZetGovLzEQb6MwoEpMQnvCSomMC9o0P776kEFy77vhrRCAdFvvrnTSpTXY2aib8hCdU5w3tQvGkoKPAAAAAAAAAAAAABP88E%2FGYNOqYg7lJ6fcoAzlVHbNi0bqTR%2Fru8noACGHR%2BtNjtq%2FnW9rBK32mcHCC5TzRDW8YYOGPax0WwgAiIBA3WMQyg%3D; source=seo.fxg.jinritemai.com; gfkadpd=4272,23756; csrf_session_id=b7b4150c5eeefaede4ef5e71473e9dc1; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1768381314; ttwid=1%7CAwu3-vdDBhOP12XdEzmCJlbyX3Qt_5RcioPVgjBIDps%7C1768381315%7Ca763fd05ed6fa274ed997007385cc0090896c597cfac0b812c962faf34f04897; tt_scid=f4YqIWnO3OdWrfVz0YVnJmYahx-qu9o9j.VZC2op7nwrQRodgrSh1ka0Ow3g5nyKd42a; odin_tt=bcf942ae72bd6b4b8f357955b71cc21199b6aec5e9acee4ce64f80704f08ea1cbaaa6e70f444f6a09712806aa424f4d0cce236e77b0bfa2991aa8a23dab27e1e; passport_auth_status=b3b3a865e0bd3857e6a28ea5a6854830%2C228cf6630632c26472c096506639ed6e; passport_auth_status_ss=b3b3a865e0bd3857e6a28ea5a6854830%2C228cf6630632c26472c096506639ed6e; uid_tt=4dfa662033e2e4eefe629ad8815f076f; uid_tt_ss=4dfa662033e2e4eefe629ad8815f076f; sid_tt=4cc6aa2f1a6e338ec72d663a0b611d3c; sessionid=4cc6aa2f1a6e338ec72d663a0b611d3c; sessionid_ss=4cc6aa2f1a6e338ec72d663a0b611d3c; PHPSESSID=a1b2fd062c1346e5c6f94bac3073cd7d; PHPSESSID_SS=a1b2fd062c1346e5c6f94bac3073cd7d; ucas_c0=CkEKBTEuMC4wEJOIgezc9NazaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Cpt53LBkip69nNBlC_vL6Ekt3t1GdYbhIU2LuS6yHmC8_SKu9Jok5ToGxfQIg; ucas_c0_ss=CkEKBTEuMC4wEJOIgezc9NazaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Cpt53LBkip69nNBlC_vL6Ekt3t1GdYbhIU2LuS6yHmC8_SKu9Jok5ToGxfQIg; ecom_gray_shop_id=156231010; sid_guard=4cc6aa2f1a6e338ec72d663a0b611d3c%7C1768381360%7C5184000%7CSun%2C+15-Mar-2026+09%3A02%3A40+GMT; session_tlb_tag=sttt%7C4%7CTMaqLxpuM47HLWY6C2EdPP________-x3_oZvMYjz8-Uw3dAm6JiPFDhS1ih9XTV79AgAO_5cvo%3D; sid_ucp_v1=1.0.0-KGRmNzNkZjM2YjUwZDk2M2M0MjQ5MGE2NzNkNGZkZjNhZWFhYmJkMmIKGQib1oDYuM3aBxCwt53LBhiwISAMOAZA9AcaAmxmIiA0Y2M2YWEyZjFhNmUzMzhlYzcyZDY2M2EwYjYxMWQzYw; ssid_ucp_v1=1.0.0-KGRmNzNkZjM2YjUwZDk2M2M0MjQ5MGE2NzNkNGZkZjNhZWFhYmJkMmIKGQib1oDYuM3aBxCwt53LBhiwISAMOAZA9AcaAmxmIiA0Y2M2YWEyZjFhNmUzMzhlYzcyZDY2M2EwYjYxMWQzYw; COMPASS_LUOPAN_DT=session_7595137429020049706; BUYIN_SASID=SID2_7595138116287152420"
cfg.Cookie = "s_v_web_id=verify_mm0pjkt7_rRCYDU7B_F5Yl_4UYj_8yQ0_ue0vAcKwYt3z; passport_csrf_token=fe2b51efeb70763190b402f49ad9f0e9; passport_csrf_token_default=fe2b51efeb70763190b402f49ad9f0e9; is_staff_user=false; Hm_lvt_b6520b076191ab4b36812da4c90f7a5e=1772792724,1773139856,1773407744,1773419459; HMACCOUNT=74DD13C46DE836FC; Hm_lpvt_b6520b076191ab4b36812da4c90f7a5e=1773419460; ttwid=1%7C71OUHp7yB34JMc3dVW9XMZxKJfcmzgfSzG407fx6Gqo%7C1773419462%7C940ba119bf375a540dbfb48b29ee8a53cc5ba26577ce549403b05104b21f131a; tt_scid=.nFHMbvBFFlz8.mmqZBwKQjwz0N1JvQ1I1w2MyHaGZmu3BVy3yPKL2ncw-E.ivZb5a00; odin_tt=0713b49299b28eef2e88f31274e12df7b1c89868218304a69fd95ec6182b253f256dc7582fcb2126d41015cc56ba2dd42664995c96204c5ae6006feaee932d5f; passport_auth_status=7fa995a5f448285c6c61bcd602ad9187%2Ce971f44a8160083bd1abde91277b1f99; passport_auth_status_ss=7fa995a5f448285c6c61bcd602ad9187%2Ce971f44a8160083bd1abde91277b1f99; uid_tt=adbd52561542b0cf10860338efe190fb; uid_tt_ss=adbd52561542b0cf10860338efe190fb; sid_tt=ab489ae34b28f997213fff3280b71961; sessionid=ab489ae34b28f997213fff3280b71961; sessionid_ss=ab489ae34b28f997213fff3280b71961; PHPSESSID=9aa8f439691b30a250dfc0235abc9e3e; PHPSESSID_SS=9aa8f439691b30a250dfc0235abc9e3e; ucas_c0=CkEKBTEuMC4wEKSIgpyE-47aaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Dp99DNBkjpq43QBlC_vL6Ekt3t1GdYbhIU-PnTyhrNrbd5k-QhCp6F7QMD3L0; ucas_c0_ss=CkEKBTEuMC4wEKSIgpyE-47aaRjmJiD61rDnqc2DBCiwITCb1oDYuM3aB0Dp99DNBkjpq43QBlC_vL6Ekt3t1GdYbhIU-PnTyhrNrbd5k-QhCp6F7QMD3L0; zsgw_business_data=%7B%22uuid%22%3A%2267d1b0e4-dca5-484d-997a-b70cb555e396%22%2C%22platform%22%3A%22pc%22%2C%22source%22%3A%22seo.fxg.jinritemai.com%22%7D; source=seo.fxg.jinritemai.com; gfkadpd=4272,23756; ecom_gray_shop_id=156231010; csrf_session_id=1579f92b6914e7cbfbc81471e18918fc; BUYIN_SASID=SID2_7616774858777182473; sid_guard=ab489ae34b28f997213fff3280b71961%7C1773419504%7C5184000%7CTue%2C+12-May-2026+16%3A31%3A44+GMT; session_tlb_tag=sttt%7C14%7Cq0ia40so-ZchP_8ygLcZYf_________D7K6IkHAPtWpxy0TIIHAoYAfUVdc7T8ZGVSu1iVCn8ZE%3D; sid_ucp_v1=1.0.0-KDMyOGFkNjY4OTZjM2NjNTM3ODNkMzFkMmVjMDIwOGRhMWQ2YmRmYjAKGQib1oDYuM3aBxDw99DNBhiwISAMOAZA9AcaAmxxIiBhYjQ4OWFlMzRiMjhmOTk3MjEzZmZmMzI4MGI3MTk2MQ; ssid_ucp_v1=1.0.0-KDMyOGFkNjY4OTZjM2NjNTM3ODNkMzFkMmVjMDIwOGRhMWQ2YmRmYjAKGQib1oDYuM3aBxDw99DNBhiwISAMOAZA9AcaAmxxIiBhYjQ4OWFlMzRiMjhmOTk3MjEzZmZmMzI4MGI3MTk2MQ; COMPASS_LUOPAN_DT=session_7616776334971060490"
}
startTime := time.Now().Add(-duration)

View File

@ -113,12 +113,12 @@ func (s *service) SubmitAddressShare(ctx context.Context, shareToken string, nam
s.logger.Info("SubmitAddressShare: Processing", zap.Int64("invID", claims.InventoryID), zap.Int64("owner", claims.OwnerUserID))
// 1. 确定资产最终归属地 (实名转赠逻辑)
targetUserID := claims.OwnerUserID
isTransfer := false
if submittedByUserID != nil && *submittedByUserID > 0 && *submittedByUserID != claims.OwnerUserID {
targetUserID = *submittedByUserID
isTransfer = true
// 必须登录才能提交submittedByUserID 由 API 层保证非空
if submittedByUserID == nil || *submittedByUserID <= 0 {
return 0, fmt.Errorf("login_required")
}
targetUserID := *submittedByUserID
isTransfer := targetUserID != claims.OwnerUserID
var addrID int64
err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {

BIN
web/.DS_Store vendored

Binary file not shown.