logo和配置修改

This commit is contained in:
谢宇宁
2026-09-23 16:50:34 +07:00
parent bef9f8b4ed
commit 0db8087765
163 changed files with 393 additions and 20366 deletions
+1
View File
@@ -12,6 +12,7 @@ func Application(c *gin.Context) {
vm := applistser.BuildPageVM(raw)
data := gin.H{
"title": "福利应用",
"pageName": "应用推荐",
"navActive": "application",
"categories": vm.Categories,
}
-548
View File
@@ -1,548 +0,0 @@
package web
import (
"net/http"
"strings"
"videoweb/app/service/authser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type registerBody struct {
AccountNo string `json:"accountNo"`
Email string `json:"email"`
Password string `json:"password"`
AgentInviteCode string `json:"agentInviteCode"`
DevID string `json:"devID"`
Channel string `json:"channel"`
}
type loginBody struct {
Account string `json:"account"`
Password string `json:"password"`
DevID string `json:"devID"`
Channel string `json:"channel"`
}
type resetPasswordBody struct {
AccountNo string `json:"accountNo"`
Email string `json:"email"`
NewPassword string `json:"newPassword"`
}
type updateProfileBody struct {
Avatar string `json:"avatar"`
Introduction string `json:"introduction"`
NickName string `json:"nickName"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
PrivacyMode *bool `json:"privacyMode"`
}
// Register 代理上游注册,成功后写入登录 cookie。
func Register(c *gin.Context) {
var body registerBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.AccountNo = strings.TrimSpace(body.AccountNo)
body.Email = strings.TrimSpace(body.Email)
body.AgentInviteCode = strings.TrimSpace(body.AgentInviteCode)
body.DevID = strings.TrimSpace(body.DevID)
body.Channel = strings.TrimSpace(body.Channel)
if body.AccountNo == "" || body.Password == "" {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请填写账号和密码",
"data": nil,
})
return
}
// 上游 registerReq 仍要求 email;前端已去掉邮箱输入时,用账号生成唯一占位邮箱。
if body.Email == "" {
body.Email = body.AccountNo + "@qmdj.local"
}
env, user, err := authser.Register(c, authser.RegisterReq{
AccountNo: body.AccountNo,
Email: body.Email,
Password: body.Password,
AgentInviteCode: body.AgentInviteCode,
DevID: body.DevID,
Channel: body.Channel,
})
if err != nil {
log.Error("register upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "register failed",
"tip": "注册失败,请稍后重试",
"data": nil,
})
return
}
if env.Code != 200 || user == nil {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "注册失败"
}
c.JSON(http.StatusOK, gin.H{
"code": env.Code,
"msg": env.Msg,
"tip": tip,
"data": nil,
})
return
}
authser.SetAuthCookies(c, user)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": env.Msg,
"tip": firstNonEmpty(env.Tip, "注册成功"),
"data": user,
})
}
// Login 代理上游登录,成功后写入登录 cookie。
func Login(c *gin.Context) {
var body loginBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.Account = strings.TrimSpace(body.Account)
body.DevID = strings.TrimSpace(body.DevID)
body.Channel = strings.TrimSpace(body.Channel)
if body.Account == "" || body.Password == "" {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请填写账号和密码",
"data": nil,
})
return
}
env, user, err := authser.Login(c, authser.LoginReq{
Account: body.Account,
Password: body.Password,
DevID: body.DevID,
Channel: body.Channel,
})
if err != nil {
log.Error("login upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "login failed",
"tip": "登录失败,请稍后重试",
"data": nil,
})
return
}
if env.Code != 200 || user == nil {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "登录失败"
}
c.JSON(http.StatusOK, gin.H{
"code": env.Code,
"msg": env.Msg,
"tip": tip,
"data": nil,
})
return
}
authser.SetAuthCookies(c, user)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": env.Msg,
"tip": firstNonEmpty(env.Tip, "登录成功"),
"data": user,
})
}
// ResetPassword 代理上游找回密码接口。
func ResetPassword(c *gin.Context) {
var body resetPasswordBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1001, "msg": "参数错误", "tip": "参数错误", "data": nil})
return
}
body.AccountNo = strings.TrimSpace(body.AccountNo)
body.Email = strings.TrimSpace(body.Email)
if body.AccountNo == "" || body.Email == "" || body.NewPassword == "" {
c.JSON(http.StatusOK, gin.H{"code": 1001, "msg": "参数错误", "tip": "请填写账号、邮箱和新密码", "data": nil})
return
}
env, err := authser.ResetPassword(c, authser.ResetPasswordReq{
AccountNo: body.AccountNo,
Email: body.Email,
NewPassword: body.NewPassword,
})
if err != nil {
log.Error("reset password upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "reset password failed", "tip": "重置密码失败,请稍后重试", "data": nil})
return
}
tip := firstNonEmpty(env.Tip, env.Msg)
if env.Code == 200 {
tip = firstNonEmpty(tip, "密码重置成功")
} else {
tip = firstNonEmpty(tip, "密码重置失败")
}
c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data})
}
// Guest 代理上游游客登录,成功后写入登录 cookie。
// 对应 POST /api/app/auth/guest;需传 devID,上游要求 X-User-Agent(由 BFF 组装)。
func Guest(c *gin.Context) {
var body struct {
DevID string `json:"devID"`
DeviceID string `json:"deviceId"`
Mac string `json:"mac"`
Channel string `json:"channel"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.DevID = strings.TrimSpace(body.DevID)
body.DeviceID = strings.TrimSpace(body.DeviceID)
body.Mac = strings.TrimSpace(body.Mac)
body.Channel = strings.TrimSpace(body.Channel)
if body.DevID == "" {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少设备标识",
"data": nil,
})
return
}
if body.DeviceID == "" {
body.DeviceID = body.DevID
}
env, user, err := authser.GuestLogin(c, authser.GuestLoginReq{
DevID: body.DevID,
DeviceID: body.DeviceID,
Mac: body.Mac,
Channel: body.Channel,
})
if err != nil {
log.Error("guest login upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "guest login failed",
"tip": "游客登录失败,请稍后重试",
"data": nil,
})
return
}
if env.Code != 200 || user == nil {
tip := strings.TrimSpace(env.Tip)
if tip == "" {
tip = strings.TrimSpace(env.Msg)
}
if tip == "" {
tip = "游客登录失败"
}
c.JSON(http.StatusOK, gin.H{
"code": env.Code,
"msg": env.Msg,
"tip": tip,
"data": nil,
})
return
}
authser.SetAuthCookies(c, user)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "游客登录成功"),
"data": user,
})
}
// UpdateProfile 代理上游修改资料,成功后刷新 getInfo 并写 cookie。
func UpdateProfile(c *gin.Context) {
var body updateProfileBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.Avatar = strings.TrimSpace(body.Avatar)
body.Introduction = strings.TrimSpace(body.Introduction)
body.NickName = strings.TrimSpace(body.NickName)
body.OldPassword = strings.TrimSpace(body.OldPassword)
body.NewPassword = strings.TrimSpace(body.NewPassword)
req := authser.UpdateProfileReq{
Avatar: body.Avatar,
Introduction: body.Introduction,
NickName: body.NickName,
OldPassword: body.OldPassword,
NewPassword: body.NewPassword,
PrivacyMode: body.PrivacyMode,
}
if req.Avatar == "" && req.Introduction == "" && req.NickName == "" && req.OldPassword == "" && req.NewPassword == "" && req.PrivacyMode == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请至少修改一项资料",
"data": nil,
})
return
}
if (req.OldPassword == "") != (req.NewPassword == "") {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "修改密码需同时填写旧密码和新密码",
"data": nil,
})
return
}
env, err := authser.UpdateProfile(c, req)
if err != nil {
log.Error("update profile upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "update profile failed",
"tip": "保存失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = authser.UpdateProfile(c, req)
if err != nil {
log.Error("update profile retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "update profile failed",
"tip": "保存失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "保存失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
// 上游成功后拉最新资料写 cookie
_, user, infoErr := authser.GetInfo(c)
if infoErr != nil || user == nil {
log.Error("update profile getInfo failed", log.E(infoErr))
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": env.Msg,
"tip": firstNonEmpty(env.Tip, "保存成功"),
"data": nil,
})
return
}
authser.SetAuthCookies(c, user)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": env.Msg,
"tip": firstNonEmpty(env.Tip, "保存成功"),
"data": user,
})
}
// UploadImage 代理上游图片上传(头像不加 watermark)。
func UploadImage(c *gin.Context) {
fileHeader, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请选择图片文件",
"data": nil,
})
return
}
f, err := fileHeader.Open()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "open file failed",
"tip": "读取图片失败",
"data": nil,
})
return
}
defer f.Close()
resp, err := authser.UploadImage(c, fileHeader.Filename, f)
if err != nil {
log.Error("upload image upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "upload failed",
"tip": "上传失败,请稍后重试",
"data": nil,
})
return
}
if resp == nil || resp.Code != 200 || resp.Data == nil || strings.TrimSpace(resp.Data.SourceNameFs) == "" {
tip := "上传失败"
msg := ""
code := 500
if resp != nil {
code = resp.Code
msg = resp.Msg
tip = firstNonEmpty(resp.Tip, resp.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(resp.Msg, "success"),
"tip": firstNonEmpty(resp.Tip, "上传成功"),
"data": gin.H{
"id": resp.Data.ID,
"sourceNameFs": resp.Data.SourceNameFs,
"url": resp.Data.SourceNameFs,
},
})
}
// GetUserInfo 代理上游 GET /api/app/auth/getInfo。
func GetUserInfo(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录",
"data": nil,
})
return
}
env, user, err := authser.GetInfo(c)
if err != nil {
log.Error("getInfo upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "getInfo failed",
"tip": "获取用户信息失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, user, err = authser.GetInfo(c)
if err != nil {
log.Error("getInfo retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "getInfo failed",
"tip": "获取用户信息失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 || user == nil {
tip := "获取用户信息失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
authser.SetAuthCookies(c, user)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": user,
})
}
func firstNonEmpty(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return v
}
}
return ""
}
-57
View File
@@ -1,57 +0,0 @@
package web
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// LoginEntryURL 首页弹窗登录入口;redirectPath 为登录成功后回跳路径(须为站内相对路径)。
func LoginEntryURL(redirectPath string) string {
redirectPath = strings.TrimSpace(redirectPath)
if i := strings.IndexByte(redirectPath, '?'); i >= 0 {
redirectPath = redirectPath[:i]
}
if !isSafeRedirect(redirectPath) || redirectPath == "/" {
return "/login/"
}
return "/login/return" + ensureRedirectTrailingSlash(redirectPath)
}
// RegisterEntryURL 首页弹窗注册入口。
func RegisterEntryURL() string {
return "/register/"
}
func ensureRedirectTrailingSlash(path string) string {
if i := strings.IndexByte(path, '?'); i >= 0 {
path = path[:i]
}
if path != "/" && !strings.HasSuffix(path, "/") {
path += "/"
}
return path
}
// RedirectToLogin 未登录访问受保护页时,跳到首页并打开登录弹窗。
func RedirectToLogin(c *gin.Context) {
path := ""
if c != nil && c.Request != nil && c.Request.URL != nil {
path = c.Request.URL.RequestURI()
}
if path == "" || path == "/login" || strings.HasPrefix(path, "/login/") {
path = "/"
}
c.Redirect(http.StatusFound, LoginEntryURL(path))
}
func isSafeRedirect(path string) bool {
if path == "" || !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") {
return false
}
if strings.ContainsAny(path, "\r\n") {
return false
}
return true
}
-5
View File
@@ -4,10 +4,8 @@ import (
"net/http"
"strconv"
"strings"
"videoweb/app/service/authser"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
@@ -38,9 +36,6 @@ func ChapterDetail(c *gin.Context) {
})
return
}
if data != nil && useragent.TokenFromRequest(c.Request) != "" {
authser.SetRechargeCookie(c, data.Balance)
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
-196
View File
@@ -1,196 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type chapterLikeBody struct {
ChapterID int64 `json:"chapterId"`
IsLike *bool `json:"isLike"`
}
// ChapterLikeSet 代理上游 POST /api/app/playlet/chapter/like/set。
func ChapterLikeSet(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再点赞",
"data": nil,
})
return
}
var body chapterLikeBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ChapterID <= 0 || body.IsLike == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 chapterId 或 isLike",
"data": nil,
})
return
}
env, err := playletser.ChapterLikeSet(c, playletser.PlayletChapterLikeSetReq{
ChapterID: body.ChapterID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("chapter like set upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.ChapterLikeSet(c, playletser.PlayletChapterLikeSetReq{
ChapterID: body.ChapterID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("chapter like set retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "操作失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
type chapterCollectBody struct {
ChapterID int64 `json:"chapterId"`
IsCollect *bool `json:"isCollect"`
}
// ChapterCollectSet 代理上游 POST /api/app/playlet/chapter/collect/set。
func ChapterCollectSet(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再收藏",
"data": nil,
})
return
}
var body chapterCollectBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ChapterID <= 0 || body.IsCollect == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 chapterId 或 isCollect",
"data": nil,
})
return
}
env, err := playletser.ChapterCollectSet(c, playletser.PlayletChapterCollectSetReq{
ChapterID: body.ChapterID,
IsCollect: *body.IsCollect,
})
if err != nil {
log.Error("chapter collect set upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter collect failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.ChapterCollectSet(c, playletser.PlayletChapterCollectSetReq{
ChapterID: body.ChapterID,
IsCollect: *body.IsCollect,
})
if err != nil {
log.Error("chapter collect set retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter collect failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "操作失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
-106
View File
@@ -1,106 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/authser"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type chapterUnlockBody struct {
ChapterID int64 `json:"chapterId"`
PlayletID int64 `json:"playletId"`
}
// ChapterUnlock 代理上游 POST /api/app/playlet/chapter/unlock。
func ChapterUnlock(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再解锁",
"data": nil,
})
return
}
var body chapterUnlockBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ChapterID <= 0 || body.PlayletID <= 0 {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 chapterId 或 playletId",
"data": nil,
})
return
}
env, err := playletser.ChapterUnlock(c, playletser.PlayletChapterUnlockReq{
ChapterID: body.ChapterID,
PlayletID: body.PlayletID,
})
if err != nil {
log.Error("chapter unlock upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter unlock failed",
"tip": "解锁失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.ChapterUnlock(c, playletser.PlayletChapterUnlockReq{
ChapterID: body.ChapterID,
PlayletID: body.PlayletID,
})
if err != nil {
log.Error("chapter unlock retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter unlock failed",
"tip": "解锁失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "解锁失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
authser.SetRechargeCookie(c, env.Data.Balance)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "解锁成功"),
"data": env.Data,
})
}
-103
View File
@@ -1,103 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type chapterWatchReportBody struct {
ChapterID int64 `json:"chapterId"`
PlayletID int64 `json:"playletId"`
}
// ChapterWatchReport 代理上游 POST /api/app/playlet/chapter/watch/report。
func ChapterWatchReport(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录",
"data": nil,
})
return
}
var body chapterWatchReportBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ChapterID <= 0 {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 chapterId",
"data": nil,
})
return
}
env, err := playletser.ChapterWatchReport(c, playletser.PlayletChapterWatchReportReq{
ChapterID: body.ChapterID,
PlayletID: body.PlayletID,
})
if err != nil {
log.Error("chapter watch report upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter watch report failed",
"tip": "上报失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.ChapterWatchReport(c, playletser.PlayletChapterWatchReportReq{
ChapterID: body.ChapterID,
PlayletID: body.PlayletID,
})
if err != nil {
log.Error("chapter watch report retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "chapter watch report failed",
"tip": "上报失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "上报失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
-84
View File
@@ -1,84 +0,0 @@
package web
import (
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
// Collect 我的收藏 SSR:
// 短剧 GET /api/app/playlet/collect/list;剧集 GET /api/app/playlet/chapter/collect/list。
func Collect(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
RedirectToLogin(c)
return
}
q := playletser.ParseCollectListQuery(routeOrQuery(c, "page", "page"), routeOrQuery(c, "type", "type"))
if redirectCanonical(c, playletser.CollectCanonicalPath(q)) {
return
}
data := gin.H{
"title": "我的收藏",
"description": "查看你在黄果短剧收藏的短剧与剧集,随时继续追更喜欢的内容。",
"navActive": "userCenter",
"collectType": q.Type,
"pageNum": q.PageNum,
"pageSize": q.PageSize,
"totalCount": int64(0),
"pagination": playletser.BuildCollectPagination(q, 0),
}
setNoIndex(data, false)
if q.Type == playletser.CollectTypeChapter {
raw, code, err := playletser.FetchChapterCollectList(c, q)
if err != nil {
aborted, retry := AbortSSRIfSessionExpired(c, code)
if aborted {
return
}
if retry {
raw, _, err = playletser.FetchChapterCollectList(c, q)
}
if err != nil {
log.Error("fetch playlet chapter collect list failed", log.E(err))
raw = &playletser.PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
}
if raw == nil {
raw = &playletser.PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
data["chapterList"] = raw.List
data["pageNum"] = raw.PageNum
data["pageSize"] = raw.PageSize
data["totalCount"] = raw.TotalCount
data["pagination"] = playletser.BuildCollectPagination(q, raw.TotalCount)
} else {
raw, code, err := playletser.FetchCollectList(c, q)
if err != nil {
aborted, retry := AbortSSRIfSessionExpired(c, code)
if aborted {
return
}
if retry {
raw, _, err = playletser.FetchCollectList(c, q)
}
if err != nil {
log.Error("fetch playlet collect list failed", log.E(err))
raw = &playletser.PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
}
if raw == nil {
raw = &playletser.PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
data["list"] = raw.List
data["pageNum"] = raw.PageNum
data["pageSize"] = raw.PageSize
data["totalCount"] = raw.TotalCount
data["pagination"] = playletser.BuildCollectPagination(q, raw.TotalCount)
}
HTMLOK(c, "collect.html", data)
}
-228
View File
@@ -3,11 +3,8 @@ package web
import (
"net/http"
"strconv"
"strings"
"unicode/utf8"
"videoweb/app/service/commentser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
@@ -55,228 +52,3 @@ func CommentList(c *gin.Context) {
"data": data,
})
}
type commentSendBody struct {
ObjectID int64 `json:"objectId"`
ObjectType int `json:"objectType"`
Text string `json:"text"`
ParentsID int64 `json:"parentsId"`
ReplyCommentID int64 `json:"replyCommentId"`
}
// CommentSend 代理上游 POST /api/app/comment/send。
// parentsId=0 一级评论;parentsId>0 二级评论;replyCommentId 为实际被回复评论。
func CommentSend(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再发表评论",
"data": nil,
})
return
}
var body commentSendBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.Text = strings.TrimSpace(body.Text)
if body.ObjectID <= 0 {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 objectId",
"data": nil,
})
return
}
if body.ObjectType != commentser.ObjectTypeChapter {
body.ObjectType = commentser.ObjectTypePlaylet
}
if body.Text == "" {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请输入评论内容",
"data": nil,
})
return
}
if utf8.RuneCountInString(body.Text) > 200 {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "评论最多 200 字",
"data": nil,
})
return
}
if body.ParentsID < 0 {
body.ParentsID = 0
}
if body.ReplyCommentID < 0 {
body.ReplyCommentID = 0
}
env, err := commentser.Send(c, commentser.CommentSendReq{
ObjectID: body.ObjectID,
ObjectType: body.ObjectType,
Text: body.Text,
ParentsID: body.ParentsID,
ReplyCommentID: body.ReplyCommentID,
})
if err != nil {
log.Error("comment send upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "comment send failed",
"tip": "发表失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = commentser.Send(c, commentser.CommentSendReq{
ObjectID: body.ObjectID,
ObjectType: body.ObjectType,
Text: body.Text,
ParentsID: body.ParentsID,
ReplyCommentID: body.ReplyCommentID,
})
if err != nil {
log.Error("comment send retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "comment send failed",
"tip": "发表失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "发表失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "发表成功"),
"data": env.Data,
})
}
type commentLikeBody struct {
CommentID int64 `json:"commentId"`
IsLike *bool `json:"isLike"`
}
// CommentLikeSet 代理上游 POST /api/app/comment/like/set。
func CommentLikeSet(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再点赞",
"data": nil,
})
return
}
var body commentLikeBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.CommentID <= 0 || body.IsLike == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 commentId 或 isLike",
"data": nil,
})
return
}
env, err := commentser.LikeSet(c, commentser.CommentLikeSetReq{
CommentID: body.CommentID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("comment like set upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "comment like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = commentser.LikeSet(c, commentser.CommentLikeSetReq{
CommentID: body.CommentID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("comment like set retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "comment like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "操作失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
-244
View File
@@ -1,244 +0,0 @@
package web
import (
"net/http"
"strings"
"videoweb/app/service/authser"
"videoweb/app/service/rechargeser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type buyPayBody struct {
PackageID int64 `json:"packageId"`
PayMode string `json:"payMode"`
}
// attachWalletFromGetInfo 登录态拉取 getInfo,刷新 cookie,并写入模板金币余额。
// 返回 true 表示注册账号会话失效,调用方应跳转登录。
func attachWalletFromGetInfo(c *gin.Context, data gin.H) bool {
if data == nil || useragent.TokenFromRequest(c.Request) == "" {
return false
}
env, user, err := authser.GetInfo(c)
if err != nil {
log.Error("wallet getInfo failed", log.E(err))
return false
}
if env != nil && authser.IsSessionExpiredCode(env.Code) {
retry, needLogin := authser.OnSessionExpired(c, env.Code)
if needLogin {
return true
}
if retry {
env, user, err = authser.GetInfo(c)
if err != nil {
log.Error("wallet getInfo retry failed", log.E(err))
return false
}
}
}
if env == nil || env.Code != 200 || user == nil {
return false
}
authser.SetAuthCookies(c, user)
data["userId"] = authser.FormatUserID(user.UserID)
data["recharge"] = user.Recharge
data["rechargeText"] = authser.FormatRecharge(user.Recharge)
data["vipType"] = user.VipType
data["showCardId"] = user.ShowCardID
data["showCardName"] = strings.TrimSpace(user.ShowCardName)
active := authser.IsActiveVipUser(user.VipType, user.CurrentVipExpire)
data["isVip"] = active
if active {
data["vipLabel"] = authser.VipDisplayLabel(user.ShowCardName, user.VipType)
data["vipExpireText"] = authser.FormatVipExpireText(user.CurrentVipExpire)
} else {
delete(data, "vipLabel")
delete(data, "vipExpireText")
}
return false
}
// Deposit 金币与会员充值页 SSR:套餐列表 + 金币流水明细。
// Query tab=coin 时默认选中金币充值套餐。
func Deposit(c *gin.Context) {
// 先补游客会话,保证可拉流水。
authser.EnsureGuestOnEnter(c)
preferTab := routeOrQuery(c, "tab", "tab")
if hasQuery(c, "tab") {
target := "/deposit"
if preferTab != "" {
target += "/" + preferTab
}
target += "/"
if redirectCanonical(c, target) {
return
}
}
raw := rechargeser.FetchPackageListOrEmpty(c)
vm := rechargeser.BuildDepositPageVM(raw, preferTab)
if useragent.TokenFromRequest(c.Request) != "" {
list, code, err := rechargeser.FetchCoinTransList(c, 1, 20)
if err != nil {
retry, needLogin := authser.OnSessionExpired(c, code)
if needLogin {
RedirectToLogin(c)
return
}
if retry {
list, _, err = rechargeser.FetchCoinTransList(c, 1, 20)
}
if err != nil {
log.Error("fetch coin trans list failed", log.E(err))
list = nil
}
}
vm.AttachTxList(list)
}
data := gin.H{
"title": "金币与会员",
"vipList": vm.VipList,
"coinList": vm.CoinList,
"payMethods": vm.PayMethods,
"txList": vm.TxList,
"preferTab": preferTab,
}
setNoIndex(data, false)
if attachWalletFromGetInfo(c, data) {
RedirectToLogin(c)
return
}
HTMLOK(c, "deposit.html", data)
}
// CoinBuy 代理上游 POST /api/app/coin/buy。
func CoinBuy(c *gin.Context) {
handleBuyPay(c, "coin")
}
// VipPay 代理上游 POST /api/app/vip/pay。
func VipPay(c *gin.Context) {
handleBuyPay(c, "vip")
}
func handleBuyPay(c *gin.Context, kind string) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再支付",
"data": nil,
})
return
}
var body buyPayBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
body.PayMode = rechargeser.NormalizePayMode(body.PayMode)
if body.PackageID <= 0 || body.PayMode == "" {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "请选择套餐和支付方式",
"data": nil,
})
return
}
req := rechargeser.BuyPayReq{
PackageID: body.PackageID,
PayMode: body.PayMode,
}
var (
env *rechargeser.APIEnvelope[rechargeser.PayOrderResp]
err error
)
switch kind {
case "coin":
env, err = rechargeser.BuyCoin(c, req)
default:
env, err = rechargeser.VipPay(c, req)
}
if err != nil {
log.Error("buy/pay upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "pay failed",
"tip": "发起支付失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
switch kind {
case "coin":
env, err = rechargeser.BuyCoin(c, req)
default:
env, err = rechargeser.VipPay(c, req)
}
if err != nil {
log.Error("buy/pay retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "pay failed",
"tip": "发起支付失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "发起支付失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
payURL := strings.TrimSpace(env.Data.PayURL)
if payURL == "" {
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "empty payUrl",
"tip": "未获取到支付链接,请稍后重试",
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "请完成支付"),
"data": gin.H{
"isOpenNewBrowser": env.Data.IsOpenNewBrowser,
"payUrl": payURL,
"payUrlMode": env.Data.PayURLMode,
"tradeNo": env.Data.TradeNo,
},
})
}
-2
View File
@@ -9,8 +9,6 @@ import (
var dialogTemplates = map[string]string{
"download": "downloadAppDialog",
"login": "loginDialog",
"register": "registerDialog",
"search": "searchDialog",
}
+3 -14
View File
@@ -4,9 +4,9 @@ import (
"net/http"
"strings"
"unicode/utf8"
"videoweb/app/service/authser"
"videoweb/app/service/feedbackser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
@@ -28,15 +28,7 @@ type contactUsBody struct {
// AddFeedback 代理上游 POST /api/app/feedback/add。
func AddFeedback(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再提交反馈",
"data": nil,
})
return
}
authser.EnsureGuestOnEnter(c)
var body feedbackBody
if err := c.ShouldBindJSON(&body); err != nil {
@@ -158,10 +150,7 @@ func AddFeedback(c *gin.Context) {
// AddContactUs 代理上游 POST /api/app/contactus/add。
func AddContactUs(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录后再提交", "data": nil})
return
}
authser.EnsureGuestOnEnter(c)
var body contactUsBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1001, "msg": "参数错误", "tip": "参数错误", "data": nil})
-117
View File
@@ -1,117 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
// History 观看历史 SSR:GET /api/app/playlet/history/list。
func History(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
RedirectToLogin(c)
return
}
q := playletser.ParseHistoryListQuery(routeOrQuery(c, "page", "page"))
if redirectCanonical(c, playletser.HistoryCanonicalPath(q)) {
return
}
raw, code, err := playletser.FetchHistoryList(c, q)
if err != nil {
aborted, retry := AbortSSRIfSessionExpired(c, code)
if aborted {
return
}
if retry {
raw, _, err = playletser.FetchHistoryList(c, q)
}
if err != nil {
log.Error("fetch playlet history list failed", log.E(err))
raw = &playletser.PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
}
if raw == nil {
raw = &playletser.PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize}
}
data := gin.H{
"title": "观看历史",
"description": "查看黄果短剧观看历史,快速回到上次看到的剧集继续观看。",
"navActive": "userCenter",
"list": raw.List,
"pageNum": raw.PageNum,
"pageSize": raw.PageSize,
"totalCount": raw.TotalCount,
"pagination": playletser.BuildHistoryPagination(q, raw.TotalCount),
"emptyTitle": "暂无观看历史",
"emptyDesc": "开始播放短剧后,记录会出现在这里",
}
setNoIndex(data, false)
HTMLOK(c, "history.html", data)
}
// HistoryClear 代理上游 POST /api/app/playlet/history/clear。
func HistoryClear(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录",
"data": nil,
})
return
}
env, err := playletser.HistoryClear(c)
if err != nil {
log.Error("playlet history clear upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "history clear failed",
"tip": "清空失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.HistoryClear(c)
if err != nil {
log.Error("playlet history clear retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "history clear failed",
"tip": "清空失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "清空失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "已清空观看历史"),
"data": env.Data,
})
}
-16
View File
@@ -8,17 +8,6 @@ import (
// Index 首页 SSR:拉取 /api/app/playlet/home,直接用接口字段渲染。
func Index(c *gin.Context) {
renderIndex(c, "")
}
func AuthIndex(mode string) gin.HandlerFunc {
return func(c *gin.Context) {
renderIndex(c, mode)
}
}
func renderIndex(c *gin.Context, authMode string) {
authRedirect := c.Param("redirect")
raw := playletser.FetchHomeOrEmpty(c)
data := gin.H{
"title": "黄果短剧",
@@ -29,11 +18,6 @@ func renderIndex(c *gin.Context, authMode string) {
"homeCategoryData": raw.HomeCategoryData,
"homeRecommendThemes": raw.HomeRecommendThemes,
"homeBannerData": playletser.ActiveHomeBanners(raw.HomeBannerData),
"authMode": authMode,
"authRedirect": authRedirect,
}
if authMode != "" {
setNoIndex(data, true)
}
HTMLOK(c, "index.html", data)
}
-6
View File
@@ -2,9 +2,7 @@ package web
import (
"strings"
"videoweb/app/service/authser"
"videoweb/app/service/playletser"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
@@ -56,10 +54,6 @@ func Play(c *gin.Context) {
chapters = playlet.PlayletChapterInfo
episodeRanges = playletser.BuildEpisodeRanges(chapters, playlet.TotalEpisode, 30)
if chapter != nil && useragent.TokenFromRequest(c.Request) != "" {
authser.SetRechargeCookie(c, chapter.Balance)
}
seoInfo := playletser.ResolvePlayPageSEO(chapter, playlet)
data := gin.H{
-196
View File
@@ -1,196 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
type playletLikeBody struct {
ID int64 `json:"id"`
IsLike *bool `json:"isLike"`
}
// PlayletLikeSet 代理上游 POST /api/app/playlet/like/set。
func PlayletLikeSet(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再点赞",
"data": nil,
})
return
}
var body playletLikeBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ID <= 0 || body.IsLike == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 id 或 isLike",
"data": nil,
})
return
}
env, err := playletser.LikeSet(c, playletser.PlayletLikeSetReq{
ID: body.ID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("playlet like set upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "playlet like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.LikeSet(c, playletser.PlayletLikeSetReq{
ID: body.ID,
IsLike: *body.IsLike,
})
if err != nil {
log.Error("playlet like set retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "playlet like failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "操作失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
type playletCollectBody struct {
ID int64 `json:"id"`
IsCollect *bool `json:"isCollect"`
}
// PlayletCollectSet 代理上游 POST /api/app/playlet/collect/set。
func PlayletCollectSet(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
c.JSON(http.StatusOK, gin.H{
"code": 401,
"msg": "unauthorized",
"tip": "请先登录后再收藏",
"data": nil,
})
return
}
var body playletCollectBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "参数错误",
"data": nil,
})
return
}
if body.ID <= 0 || body.IsCollect == nil {
c.JSON(http.StatusOK, gin.H{
"code": 1001,
"msg": "参数错误",
"tip": "缺少 id 或 isCollect",
"data": nil,
})
return
}
env, err := playletser.CollectSet(c, playletser.PlayletCollectSetReq{
ID: body.ID,
IsCollect: *body.IsCollect,
})
if err != nil {
log.Error("playlet collect set upstream failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "playlet collect failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
if env != nil && TryJSONSessionRecover(c, env.Code) {
env, err = playletser.CollectSet(c, playletser.PlayletCollectSetReq{
ID: body.ID,
IsCollect: *body.IsCollect,
})
if err != nil {
log.Error("playlet collect set retry failed", log.E(err))
c.JSON(http.StatusOK, gin.H{
"code": 500,
"msg": "playlet collect failed",
"tip": "操作失败,请稍后重试",
"data": nil,
})
return
}
}
if env == nil || env.Code != 200 {
tip := "操作失败"
msg := ""
code := 500
if env != nil {
code = env.Code
msg = env.Msg
tip = firstNonEmpty(env.Tip, env.Msg, tip)
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"msg": msg,
"tip": tip,
"data": nil,
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": firstNonEmpty(env.Msg, "success"),
"tip": firstNonEmpty(env.Tip, "成功"),
"data": env.Data,
})
}
+1
View File
@@ -64,6 +64,7 @@ func Rank(c *gin.Context) {
}
data := gin.H{
"title": "排行榜",
"pageName": "排行榜",
"description": "黄果短剧排行榜实时更新热播短剧,查看热门榜、飙升榜,不错过高口碑必看剧集。",
"navActive": "rank",
"seoInfo": raw.SeoInfo,
+13 -77
View File
@@ -13,7 +13,7 @@ import (
"github.com/gin-gonic/gin"
)
// HTML 渲染页面并注入 footer / 导航 / 公共配置 / 登录态(以 cookie 为准)。
// HTML 渲染页面并注入 footer / 导航 / 公共配置,并确保存在游客会话。
func HTML(c *gin.Context, code int, name string, data gin.H) {
if data == nil {
data = gin.H{}
@@ -69,7 +69,6 @@ func HTML(c *gin.Context, code int, name string, data gin.H) {
if !ok {
cfg = configser.FetchAppConfigVMOrEmpty(c)
}
cfg = configser.ApplyTrackerFallback(cfg)
data["appConfig"] = cfg
siteURL := seoser.ResolveSiteURL(c, cfg)
data["foreverUrlAddr"] = siteURL
@@ -79,7 +78,6 @@ func HTML(c *gin.Context, code int, name string, data gin.H) {
canonicalURL = seoser.ContentCanonicalURL(siteURL, canonicalURL, map[string]any(data))
data["canonicalURL"] = canonicalURL
data["seoVerify"] = seoser.VerifyFromConfig()
attachTrackerPage(c, code, data)
noindex := pageNoIndex(data)
if noindex {
data["noindex"] = true
@@ -88,84 +86,14 @@ func HTML(c *gin.Context, code int, name string, data gin.H) {
data["jsonLD"] = seoser.PageJSONLD(siteURL, canonicalURL, c.Request.URL.RequestURI(), map[string]any(data), cfg)
}
// 无登录 token 时自动游客登录(POST /api/app/auth/guest),保证默认进入即有会话。
// 无 token 时静默建立游客会话(POST /api/app/auth/guest),上游内容接口依赖该会话。
authser.EnsureGuestOnEnter(c)
loggedIn, userName, avatar, email := authser.AuthFromCookies(c)
registered := authser.IsRegisteredFromCookies(c)
data["isLoggedIn"] = loggedIn
data["isRegistered"] = registered
if _, ok := data["isVip"]; !ok {
data["isVip"] = loggedIn && authser.IsActiveVipFromCookies(c)
}
isVip, _ := data["isVip"].(bool)
data["isVip"] = isVip
if loggedIn {
if userName != "" {
data["userName"] = userName
}
if avatar != "" {
data["avatar"] = avatar
}
if email != "" {
data["email"] = email
}
if _, ok := data["userId"]; !ok {
if uid := authser.UserIDFromCookies(c); uid > 0 {
data["userId"] = authser.FormatUserID(uid)
}
}
// 允许页面 handler 先注入最新钱包数据(如 getInfo),避免被 cookie 覆盖。
if _, ok := data["recharge"]; !ok {
recharge := authser.RechargeFromCookies(c)
data["recharge"] = recharge
data["rechargeText"] = authser.FormatRecharge(recharge)
} else if _, ok := data["rechargeText"]; !ok {
data["rechargeText"] = authser.FormatRecharge(toInt64(data["recharge"]))
}
if _, ok := data["vipType"]; !ok {
data["vipType"] = authser.VipTypeFromCookies(c)
}
vipType := toInt(data["vipType"])
data["vipType"] = vipType
if _, ok := data["showCardName"]; !ok {
data["showCardName"] = authser.ShowCardNameFromCookies(c)
}
showCardName, _ := data["showCardName"].(string)
data["showCardName"] = strings.TrimSpace(showCardName)
if _, ok := data["showCardId"]; !ok {
data["showCardId"] = authser.ShowCardIDFromCookies(c)
}
if isVip {
if _, ok := data["vipLabel"]; !ok {
data["vipLabel"] = authser.VipDisplayLabel(showCardName, vipType)
}
if _, ok := data["vipExpireText"]; !ok {
data["vipExpireText"] = authser.VipExpireTextFromCookies(c)
}
} else {
delete(data, "vipLabel")
delete(data, "vipExpireText")
}
} else {
delete(data, "userName")
delete(data, "avatar")
delete(data, "email")
delete(data, "userId")
delete(data, "recharge")
delete(data, "rechargeText")
delete(data, "vipType")
delete(data, "showCardId")
delete(data, "showCardName")
delete(data, "vipLabel")
delete(data, "vipExpireText")
}
c.HTML(code, name, data)
}
// buildPageConsoleData 构建浏览器控制台使用的页面返回数据快照。
// 快照在公共配置和 cookie 登录态注入前生成,避免把会话相关公共字段写入日志。
// 快照不包含公共配置等大字段。
func buildPageConsoleData(path string, code int, templateName string, data gin.H) gin.H {
pageData := make(gin.H, len(data))
for key, value := range data {
@@ -191,8 +119,7 @@ func buildPageConsoleData(path string, code int, templateName string, data gin.H
func pageConsoleExcludedKey(key string) bool {
switch key {
case "pageConsoleData", "appConfig", "footer", "headerNav", "jsonLD", "seoVerify",
"isLoggedIn", "isRegistered", "userName", "avatar", "email", "userId":
case "pageConsoleData", "appConfig", "footer", "headerNav", "jsonLD", "seoVerify":
return true
default:
return false
@@ -312,3 +239,12 @@ func toInt64(v any) int64 {
func toInt(v any) int {
return int(toInt64(v))
}
func firstNonEmpty(values ...string) string {
for _, v := range values {
if strings.TrimSpace(v) != "" {
return v
}
}
return ""
}
+2 -4
View File
@@ -7,15 +7,13 @@ import (
"github.com/gin-gonic/gin"
)
func TestBuildPageConsoleDataFiltersCommonSessionFields(t *testing.T) {
func TestBuildPageConsoleDataFiltersCommonFields(t *testing.T) {
payload := buildPageConsoleData("/detail/example", http.StatusOK, "detail.html", gin.H{
"title": "示例短剧",
"detail": gin.H{"id": 12, "slug": "example"},
"appConfig": "public config",
"headerNav": "navigation",
"footer": "footer data",
"isLoggedIn": true,
"email": "viewer@example.com",
"pageConsoleData": "old payload",
})
@@ -32,7 +30,7 @@ func TestBuildPageConsoleDataFiltersCommonSessionFields(t *testing.T) {
if payload["headerNav"] != "navigation" || payload["footer"] != "footer data" {
t.Fatalf("common navigation/footer data missing: %#v", payload)
}
for _, key := range []string{"appConfig", "headerNav", "isLoggedIn", "email", "pageConsoleData"} {
for _, key := range []string{"appConfig", "headerNav", "pageConsoleData"} {
if _, ok := pageData[key]; ok {
t.Fatalf("excluded key %q remains in console data: %#v", key, pageData)
}
+11 -9
View File
@@ -20,7 +20,7 @@ func Richtext(c *gin.Context) {
if redirectCanonical(c, target) {
return
}
renderRichtext(c, title)
renderRichtext(c, title, "富文本页")
}
func RichtextPage(title string) gin.HandlerFunc {
@@ -34,31 +34,32 @@ func RichtextPage(title string) gin.HandlerFunc {
"帮助中心": "helpCenter.html",
}
if fallbackTemplate := fallbackTemplates[title]; fallbackTemplate != "" {
data := gin.H{"title": title}
data := gin.H{"title": title, "pageName": title}
applyRichtextIndexPolicy(data, title)
if title == "帮助中心" {
data["faqItems"] = []seoser.FAQItem{
{Question: "无法登录账号怎么办?", Answer: "请确认邮箱与密码是否正确;若忘记密码,可在登录页面使用“找回密码”。"},
{Question: "观看记录没有同步?", Answer: "请确认已登录同一账号并保持网络连接,记录通常会自动同步。"},
{Question: "付费剧集怎么观看?", Answer: "网页端可免费观看免费剧集;付费剧集请下载黄果短剧 App 观看完整内容。"},
{Question: "需要注册账号吗?", Answer: "不需要,网页端无需注册即可直接观看免费剧集。"},
{Question: "视频无法播放或加载缓慢?", Answer: "建议切换网络、刷新页面或更新浏览器;问题持续时可提交反馈。"},
{Question: "如何管理收藏内容?", Answer: "进入个人中心的“追剧与收藏”页面,可查看或取消收藏。"},
{Question: "在哪里下载 App?", Answer: "点击页面上的“下载APP”入口,按提示扫码或直接下载安装。"},
}
}
HTMLOK(c, fallbackTemplate, data)
return
}
}
renderRichtextData(c, title, page)
renderRichtextData(c, title, title, page)
}
}
func renderRichtext(c *gin.Context, title string) {
func renderRichtext(c *gin.Context, title, pageName string) {
title = strings.TrimSpace(title)
page := configser.FetchRichTextByTitle(c, title)
renderRichtextData(c, title, page)
renderRichtextData(c, title, pageName, page)
}
func renderRichtextData(c *gin.Context, title string, page configser.RichTextPageVM) {
// pageName 为面包屑名称,与上游富文本标题可能不同。
func renderRichtextData(c *gin.Context, title, pageName string, page configser.RichTextPageVM) {
if !page.Found {
NotFound(c, "未找到该内容")
return
@@ -73,6 +74,7 @@ func renderRichtextData(c *gin.Context, title string, page configser.RichTextPag
"title": displayTitle,
"richtext": page,
"richtextKey": title,
"pageName": pageName,
}
applyRichtextIndexPolicy(data, title)
HTMLOK(c, "richtext.html", data)
+2 -22
View File
@@ -6,27 +6,7 @@ import (
"github.com/gin-gonic/gin"
)
// AbortSSRIfSessionExpired 处理 SSR 场景下的会话失效码。
// aborted=true:已跳转登录,调用方应立刻 return;
// retry=true:游客已重新登录,调用方应重试上游请求。
func AbortSSRIfSessionExpired(c *gin.Context, code int) (aborted, retry bool) {
if !authser.IsSessionExpiredCode(code) {
return false, false
}
retry, needLogin := authser.OnSessionExpired(c, code)
if needLogin {
RedirectToLogin(c)
return true, false
}
return false, retry
}
// TryJSONSessionRecover JSON 接口遇到会话失效时:游客重登后返回 true(应重试);
// 注册账号清 cookie 后返回 false,调用方继续把 code=1000 交给前端弹登录。
// TryJSONSessionRecover 上游返回会话失效时重建游客会话;返回 true 表示调用方应重试一次。
func TryJSONSessionRecover(c *gin.Context, code int) bool {
if !authser.IsSessionExpiredCode(code) {
return false
}
retry, _ := authser.OnSessionExpired(c, code)
return retry
return authser.RecoverContentSession(c, code)
}
-119
View File
@@ -1,119 +0,0 @@
package web
import (
"net/http"
"videoweb/app/service/authser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
// Setting 账号设置页:SSR 拉取 /api/app/auth/getInfo。
func Setting(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
RedirectToLogin(c)
return
}
env, user, err := authser.GetInfo(c)
if err != nil {
log.Error("setting getInfo failed", log.E(err))
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
if env != nil && authser.IsSessionExpiredCode(env.Code) {
retry, needLogin := authser.OnSessionExpired(c, env.Code)
if needLogin || !retry {
RedirectToLogin(c)
return
}
env, user, err = authser.GetInfo(c)
if err != nil || env == nil || env.Code != 200 || user == nil {
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
} else if env == nil || env.Code != 200 || user == nil {
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
authser.SetAuthCookies(c, user)
vm := authser.BuildSettingVM(user)
data := gin.H{
"title": "账号设置",
"setting": vm,
}
setNoIndex(data, false)
HTMLOK(c, "setting.html", data)
}
// EditProfile 编辑资料页:SSR 拉取 getInfo,头像/昵称等回填。
func EditProfile(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
RedirectToLogin(c)
return
}
env, user, err := authser.GetInfo(c)
if err != nil {
log.Error("edit-profile getInfo failed", log.E(err))
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
if env != nil && authser.IsSessionExpiredCode(env.Code) {
retry, needLogin := authser.OnSessionExpired(c, env.Code)
if needLogin || !retry {
RedirectToLogin(c)
return
}
env, user, err = authser.GetInfo(c)
if err != nil || env == nil || env.Code != 200 || user == nil {
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
} else if env == nil || env.Code != 200 || user == nil {
authser.ClearAuthCookies(c)
RedirectToLogin(c)
return
}
authser.SetAuthCookies(c, user)
vm := authser.BuildSettingVM(user)
data := gin.H{
"title": "编辑个人资料",
"profile": vm,
}
setNoIndex(data, false)
HTMLOK(c, "editProfile.html", data)
}
// EditPassword 修改密码页:需登录。
func EditPassword(c *gin.Context) {
if useragent.TokenFromRequest(c.Request) == "" {
RedirectToLogin(c)
return
}
data := gin.H{
"title": "修改登录密码",
}
setNoIndex(data, false)
HTMLOK(c, "editPassword.html", data)
}
// Logout 清除登录 cookie,并标记跳过紧接着的一次自动游客登录。
func Logout(c *gin.Context) {
authser.ClearAuthCookies(c)
authser.MarkGuestSkipAuto(c)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "success",
"tip": "已退出登录",
"data": nil,
})
}
+1
View File
@@ -19,6 +19,7 @@ func TodayUpdate(c *gin.Context) {
}
data := gin.H{
"title": "今日更新",
"pageName": "今日更新",
"description": "黄果短剧今日更新:第一时间追更最新上线短剧与连载剧集,不错过每日精彩更新。",
"navActive": "todayUpdate",
"seoInfo": raw.SeoInfo,
-127
View File
@@ -1,127 +0,0 @@
package web
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// attachTrackerPage 按响应状态和路径注入 pageKey / pageName(handler 可显式覆盖)。
// 所有 HTML 页面都必须得到明确页面信息,避免埋点落入 unknown / 未知页面。
func attachTrackerPage(c *gin.Context, code int, data gin.H) {
if data == nil {
return
}
key, name := trackerPageFromRequest(c.Request.URL.Path, code)
if code >= http.StatusBadRequest {
data["pageKey"] = key
data["pageName"] = name
return
}
existingKey, _ := data["pageKey"].(string)
existingName, _ := data["pageName"].(string)
existingKey = strings.TrimSpace(existingKey)
existingName = strings.TrimSpace(existingName)
if existingKey == "" || existingKey == "unknown" {
data["pageKey"] = key
}
if existingName == "" || existingName == "未知页面" {
data["pageName"] = name
}
}
func trackerPageFromRequest(path string, code int) (pageKey, pageName string) {
if code >= http.StatusInternalServerError {
return "server_error", "服务异常页面"
}
if code >= http.StatusBadRequest {
return "not_found", "404页面"
}
return trackerPageFromPath(path)
}
func trackerPageFromPath(path string) (pageKey, pageName string) {
p := strings.TrimSpace(path)
if p == "" || p == "/" {
return "home", "首页"
}
// 去掉尾部斜杠,便于精确匹配
p = strings.TrimSuffix(p, "/")
switch {
case trackerPathHasPrefix(p, "/list"):
return "list", "短剧列表"
case trackerPathHasPrefix(p, "/series"):
return "series", "AI成人剧集"
case trackerPathHasPrefix(p, "/tag"), trackerPathHasPrefix(p, "/tag-list"):
return "tag_list", "标签详情"
case trackerPathHasPrefix(p, "/rank"):
return "rank", "排行榜"
case trackerPathHasPrefix(p, "/today-update"):
return "today_update", "今日更新"
case trackerPathHasPrefix(p, "/category"):
return "category_detail", "分类详情"
case trackerTopicIndexPath(p):
return "theme_index", "专题列表"
case trackerPathHasPrefix(p, "/topic"), trackerPathHasPrefix(p, "/more"):
return "topic", "专题详情"
case trackerPathHasPrefix(p, "/download-app"):
return "download_app", "下载APP"
case trackerPathHasPrefix(p, "/application"):
return "application", "应用推荐"
case trackerPathHasPrefix(p, "/home-road"):
return "home_road", "回家的路"
case trackerPathHasPrefix(p, "/detail"):
return "detail", "短剧详情"
case trackerPathHasPrefix(p, "/play"):
return "play", "播放页"
case trackerPathHasPrefix(p, "/login"):
return "login", "登录"
case trackerPathHasPrefix(p, "/register"):
return "register", "注册"
case trackerPathHasPrefix(p, "/user-center"):
return "user_center", "个人中心"
case trackerPathHasPrefix(p, "/deposit"):
return "deposit", "充值中心"
case trackerPathHasPrefix(p, "/collect"):
return "collect", "我的收藏"
case trackerPathHasPrefix(p, "/history"):
return "history", "观看历史"
case trackerPathHasPrefix(p, "/setting"):
return "setting", "设置"
case trackerPathHasPrefix(p, "/edit-profile"):
return "edit_profile", "编辑资料"
case trackerPathHasPrefix(p, "/edit-password"):
return "edit_password", "修改密码"
case trackerPathHasPrefix(p, "/search"):
return "search", "搜索结果页"
case trackerPathHasPrefix(p, "/about-us"):
return "about_us", "关于我们"
case trackerPathHasPrefix(p, "/user-agreement"):
return "user_agreement", "用户协议"
case trackerPathHasPrefix(p, "/privacy-policy"):
return "privacy_policy", "隐私政策"
case trackerPathHasPrefix(p, "/help-center"):
return "help_center", "帮助中心"
case trackerPathHasPrefix(p, "/richtext"):
return "rich_text", "富文本页"
case trackerPathHasPrefix(p, "/feedback"):
return "feedback", "问题反馈"
case trackerPathHasPrefix(p, "/nav"), trackerPathHasPrefix(p, "/dh"):
return "nav", "官方导航"
default:
return "internal_other", "站内其他页面"
}
}
func trackerPathHasPrefix(path, base string) bool {
return path == base || strings.HasPrefix(path, base+"/")
}
func trackerTopicIndexPath(path string) bool {
if path == "/topic" || path == "/more" {
return true
}
return strings.HasPrefix(path, "/topic/page/") || strings.HasPrefix(path, "/more/page/")
}
-107
View File
@@ -1,107 +0,0 @@
package web
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestTrackerPageFromPath(t *testing.T) {
tests := []struct {
path string
key string
name string
}{
{"/", "home", "首页"},
{"/list/ai-short-play/2/page/3/", "list", "短剧列表"},
{"/series/hot/page/2/", "series", "AI成人剧集"},
{"/tag/family/page/2/", "tag_list", "标签详情"},
{"/tag-list/family/", "tag_list", "标签详情"},
{"/rank/2/page/3/", "rank", "排行榜"},
{"/today-update/page/2/", "today_update", "今日更新"},
{"/category/drama/page/2/", "category_detail", "分类详情"},
{"/topic/page/2/", "theme_index", "专题列表"},
{"/topic/sweet-love/page/2/", "topic", "专题详情"},
{"/more/legacy-topic/", "topic", "专题详情"},
{"/detail/my-playlet/", "detail", "短剧详情"},
{"/play/my-playlet/42/", "play", "播放页"},
{"/login/return/detail/foo/", "login", "登录"},
{"/register/", "register", "注册"},
{"/collect/chapter/page/2/", "collect", "我的收藏"},
{"/search/测试/page/2/", "search", "搜索结果页"},
{"/about-us/", "about_us", "关于我们"},
{"/user-agreement/", "user_agreement", "用户协议"},
{"/privacy-policy/", "privacy_policy", "隐私政策"},
{"/help-center/", "help_center", "帮助中心"},
{"/richtext/自定义内容/", "rich_text", "富文本页"},
{"/dh/", "nav", "官方导航"},
{"/unregistered-path/", "internal_other", "站内其他页面"},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
key, name := trackerPageFromPath(tt.path)
if key != tt.key || name != tt.name {
t.Fatalf("trackerPageFromPath(%q) = (%q, %q), want (%q, %q)", tt.path, key, name, tt.key, tt.name)
}
})
}
}
func TestTrackerPageFromRequestUsesResponseStatus(t *testing.T) {
tests := []struct {
status int
key string
name string
}{
{http.StatusNotFound, "not_found", "404页面"},
{http.StatusInternalServerError, "server_error", "服务异常页面"},
}
for _, tt := range tests {
key, name := trackerPageFromRequest("/missing/", tt.status)
if key != tt.key || name != tt.name {
t.Fatalf("trackerPageFromRequest(status=%d) = (%q, %q), want (%q, %q)", tt.status, key, name, tt.key, tt.name)
}
}
}
func TestAttachTrackerPagePreservesExplicitPage(t *testing.T) {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodGet, "/topic/custom/", nil)
data := gin.H{"pageKey": "topic", "pageName": "自定义专题"}
attachTrackerPage(c, http.StatusOK, data)
if data["pageKey"] != "topic" || data["pageName"] != "自定义专题" {
t.Fatalf("explicit tracker page was overwritten: %#v", data)
}
}
func TestAttachTrackerPageReplacesUnknownValues(t *testing.T) {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodGet, "/play/demo/1/", nil)
data := gin.H{"pageKey": "unknown", "pageName": "未知页面"}
attachTrackerPage(c, http.StatusOK, data)
if data["pageKey"] != "play" || data["pageName"] != "播放页" {
t.Fatalf("unknown tracker page was not replaced: %#v", data)
}
}
func TestAttachTrackerPageMarksErrorResponse(t *testing.T) {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodGet, "/topic/deleted/", nil)
data := gin.H{"pageKey": "topic", "pageName": "已删除专题"}
attachTrackerPage(c, http.StatusNotFound, data)
if data["pageKey"] != "not_found" || data["pageName"] != "404页面" {
t.Fatalf("error response retained business page mapping: %#v", data)
}
}
-58
View File
@@ -1,58 +0,0 @@
package web
import (
"strconv"
"videoweb/app/service/playletser"
"videoweb/pub/common/log"
"videoweb/pub/common/useragent"
"github.com/gin-gonic/gin"
)
// UserCenter 个人中心:登录态刷新 getInfo 金币余额,并拉取收藏数量。
func UserCenter(c *gin.Context) {
data := gin.H{
"title": "个人中心",
"navActive": "userCenter",
}
setNoIndex(data, false)
if attachWalletFromGetInfo(c, data) {
RedirectToLogin(c)
return
}
if useragent.TokenFromRequest(c.Request) != "" {
raw, code, err := playletser.FetchCollectList(c, playletser.CollectListQuery{
PageNum: 1,
PageSize: 1,
})
if err != nil {
aborted, retry := AbortSSRIfSessionExpired(c, code)
if aborted {
return
}
if retry {
raw, _, err = playletser.FetchCollectList(c, playletser.CollectListQuery{
PageNum: 1,
PageSize: 1,
})
}
if err != nil {
log.Error("user-center collect count failed", log.E(err))
raw = nil
}
}
count := int64(0)
if raw != nil {
count = raw.TotalCount
}
if count < 0 {
count = 0
}
data["collectCount"] = count
data["collectCountText"] = strconv.FormatInt(count, 10) + "部"
} else {
data["collectCount"] = int64(0)
data["collectCountText"] = "0部"
}
HTMLOK(c, "userCenter.html", data)
}