Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-09-29 11:47:47 +07:00
co-authored by Claude Opus 5.5
commit 0fbdba5ccf
622 changed files with 72105 additions and 0 deletions
+279
View File
@@ -0,0 +1,279 @@
package web
import (
"net/http"
"net/url"
"strconv"
"strings"
"videoweb/app/service/authser"
"videoweb/app/service/rechargeser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
const rechargePageSize = 5
// Recharge 金币充值页:套餐 GET /api/app/recharge/package/list,购买 POST /api/app/coin/buy,流水 GET /api/app/coin/trans/list。
func Recharge(c *gin.Context) {
data := gin.H{
"title": "金币充值 - 黄果短剧", "pageHeading": "金币充值",
"backHref": "/profile", "helpHref": "/help",
}
responses := gin.H{}
loadAccountSummary(c, data, responses)
packages, err := rechargeser.FetchPackageList(c)
if err != nil {
log.Error("fetch recharge packages failed", log.E(err))
data["packageUnavailable"] = true
packages = &rechargeser.RechargePackageListResp{}
responses["recharge/package/list"] = gin.H{"error": "加载失败", "msg": err.Error()}
} else {
responses["recharge/package/list"] = gin.H{"code": 200, "data": packages}
}
vm := rechargeser.BuildDepositPageVM(packages, "coin")
data["packages"] = vm.CoinList
data["paymentMethods"] = vm.PayMethods
data["rechargePackageResponse"] = packages
balance := authser.RechargeFromCookies(c)
if _, ok := data["recharge"]; ok {
balance = toInt64(data["recharge"])
}
data["balance"] = balance
data["balanceText"] = authser.FormatRecharge(balance)
page, _, err := fetchRechargeTransactions(c, 1, rechargePageSize, 0)
if err != nil {
log.Error("fetch recharge transactions failed", log.E(err))
data["transactionsUnavailable"] = true
page = &rechargeser.CoinTransPage{PageNum: 1, PageSize: rechargePageSize}
responses["coin/trans/list"] = gin.H{"error": "加载失败", "msg": err.Error()}
} else {
responses["coin/trans/list"] = gin.H{"code": 200, "data": page}
}
data["transactions"] = rechargeser.BuildTxListFromCoinTrans(page.List, 0)
data["transactionPage"] = page
data["transactionResponse"] = page
data["rechargeDebug"] = responses
HTMLOK(c, "recharge.html", data)
}
// RechargeCoinBuy BFF:代理 POST /api/app/coin/buy。
// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/post_api_app_coin_buy
func RechargeCoinBuy(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
var body rechargeser.BuyPayReq
if err := c.ShouldBindJSON(&body); err != nil || body.PackageID <= 0 {
c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "packageId required", "tip": "请选择充值套餐", "data": nil})
return
}
body.PayMode = rechargeser.NormalizePayMode(body.PayMode)
if body.PayMode == "" {
c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "payMode required", "tip": "请选择支付方式", "data": nil})
return
}
if !validCoinPackagePayment(c, body.PackageID, body.PayMode) {
c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "invalid package/payMode", "tip": "套餐或支付方式不可用,请刷新后重试", "data": nil})
return
}
env, err := rechargeser.BuyCoin(c, body)
if err != nil {
log.Error("buy coin failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil})
return
}
if env != nil {
if retry, needLogin := authser.OnSessionExpired(c, env.Code); needLogin {
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": "登录已过期,请重新登录", "data": nil})
return
} else if retry {
env, err = rechargeser.BuyCoin(c, body)
if err != nil {
log.Error("buy coin retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil})
return
}
}
}
if env == nil || env.Code != 200 {
code, tip, msg := 500, "创建支付订单失败", "buy failed"
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmptyAuth(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{"code": code, "msg": msg, "tip": tip, "data": nil})
return
}
safeURL := safePaymentURL(env.Data.PayURL)
if safeURL == "" {
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "invalid payUrl", "tip": "支付地址无效,请稍后重试", "data": nil})
return
}
env.Data.PayURL = safeURL
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": env.Msg,
"tip": firstNonEmptyAuth(env.Tip, "订单创建成功"),
"data": env.Data,
})
}
// RechargeTransactions BFF:代理 GET /api/app/coin/trans/list。
// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_list
// query:page / pageSize / type(0全部 1充值 2消费 3奖励)
func RechargeTransactions(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
page := queryInt(c, "page", 1)
pageSize := queryInt(c, "pageSize", rechargePageSize)
typ := queryIntAllowZero(c, "type", 0)
if typ < 0 || typ > 3 {
typ = 0
}
result, code, err := fetchRechargeTransactions(c, page, pageSize, typ)
if err != nil {
log.Error("fetch recharge transactions failed", log.E(err))
tip := "交易明细加载失败"
if code > 0 {
c.JSON(http.StatusOK, gin.H{"code": code, "msg": err.Error(), "tip": tip, "data": nil})
return
}
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": tip, "data": nil})
return
}
if result == nil {
result = &rechargeser.CoinTransPage{PageNum: page, PageSize: pageSize}
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "ok",
"tip": "",
"data": gin.H{
"list": rechargeser.BuildTxListFromCoinTrans(result.List, 0),
"rawList": result.List,
"pageNum": result.PageNum,
"pageSize": result.PageSize,
"totalCount": result.TotalCount,
},
})
}
// RechargeCoinTransExport BFF:代理 GET /api/app/coin/trans/export。
// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_export
// query:type(0全部 1充值 2消费 3奖励),返回 CSV。
func RechargeCoinTransExport(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil})
return
}
typ := queryIntAllowZero(c, "type", 0)
if typ < 0 || typ > 3 {
typ = 0
}
result, err := rechargeser.FetchCoinTransExport(c, typ)
if err != nil {
log.Error("export coin trans failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "导出失败,请稍后重试", "data": nil})
return
}
ct := strings.TrimSpace(result.ContentType)
if ct == "" || strings.Contains(strings.ToLower(ct), "json") {
ct = "text/csv; charset=utf-8"
}
disposition := strings.TrimSpace(result.ContentDisposition)
if disposition == "" {
disposition = `attachment; filename="coin-trans.csv"`
}
c.Header("Content-Type", ct)
c.Header("Content-Disposition", disposition)
c.Header("Cache-Control", "no-store")
c.Data(http.StatusOK, ct, result.Body)
}
func fetchRechargeTransactions(c *gin.Context, page, pageSize, typ int) (*rechargeser.CoinTransPage, int, error) {
result, code, err := rechargeser.FetchCoinTransPage(c, page, pageSize, typ)
if retry, _ := authser.OnSessionExpired(c, code); retry {
result, code, err = rechargeser.FetchCoinTransPage(c, page, pageSize, typ)
}
return result, code, err
}
// attachRechargeModal 注入充值弹窗数据:GET /api/app/recharge/package/list → coinList。
func attachRechargeModal(c *gin.Context, data gin.H) {
if data == nil {
return
}
packages := rechargeser.FetchPackageListOrEmpty(c)
vm := rechargeser.BuildDepositPageVM(packages, "coin")
data["rechargePackages"] = vm.CoinList
data["rechargePayMethods"] = vm.PayMethods
data["rechargePackageResponse"] = packages
if _, ok := data["unlockCost"]; !ok {
data["unlockCost"] = int64(0)
}
if _, ok := data["balance"]; !ok {
data["balance"] = authser.RechargeFromCookies(c)
}
if _, ok := data["balanceText"]; !ok {
data["balanceText"] = authser.FormatRecharge(toInt64(data["balance"]))
}
}
func validCoinPackagePayment(c *gin.Context, packageID int64, payMode string) bool {
data, err := rechargeser.FetchPackageList(c)
if err != nil || data == nil {
return false
}
for _, item := range data.CoinList {
if item.ID != packageID {
continue
}
for _, method := range item.PayMethods {
if rechargeser.NormalizePayMode(method.Type) == payMode {
return true
}
}
return false
}
return false
}
func safePaymentURL(raw string) string {
raw = strings.TrimSpace(raw)
u, err := url.Parse(raw)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
return ""
}
return raw
}
func queryInt(c *gin.Context, name string, fallback int) int {
v, err := strconv.Atoi(strings.TrimSpace(c.Query(name)))
if err != nil || v <= 0 {
return fallback
}
return v
}
// queryIntAllowZero 允许 0(如 type=0 全部)。
func queryIntAllowZero(c *gin.Context, name string, fallback int) int {
raw := strings.TrimSpace(c.Query(name))
if raw == "" {
return fallback
}
v, err := strconv.Atoi(raw)
if err != nil {
return fallback
}
return v
}