feat: SSR 右栏签到/抽奖条与安全回跳

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 15:55:32 +08:00
parent 959a2c3a2e
commit c4faba81b3
8 changed files with 182 additions and 12 deletions

View File

@@ -76,10 +76,17 @@ type RightAsideWidget struct {
FriendLinks []RightAsideFriendLink
}
// RightAsideData 右栏:固定热门 + 可配置 widgets
// RightAsideData 右栏:签到条 + 固定热门 + 可配置 widgets
type RightAsideData struct {
HotPosts []RightAsideHotPost
Widgets []RightAsideWidget
ShowCheckIn bool
CheckedIn bool
CheckInStreak int
CheckInPoints int // 已签实得或预计可得
LotteryDrawn bool
LotteryPoints int
LotteryCost int
HotPosts []RightAsideHotPost
Widgets []RightAsideWidget
}
// PageChrome 布局公共字段
@@ -160,7 +167,7 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
ViewerPoints: viewerPoints,
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
RightAside: d.loadRightAside(brand),
RightAside: d.loadRightAside(ctx, brand),
}
}
@@ -171,11 +178,24 @@ const (
rightAsideUserLimit = 8
)
func (d Deps) loadRightAside(brand services.SiteBranding) RightAsideData {
func (d Deps) loadRightAside(ctx *webctx.Context, brand services.SiteBranding) RightAsideData {
out := RightAsideData{
HotPosts: []RightAsideHotPost{},
Widgets: []RightAsideWidget{},
}
if ctx.IsSigned() && d.Points != nil {
out.ShowCheckIn = true
if st, err := d.Points.GetCheckInStatus(ctx.UserID()); err == nil {
out.CheckedIn = st.CheckedIn
out.CheckInStreak = st.Streak
out.CheckInPoints = st.TodayPoints
}
if st, err := d.Points.GetLotteryStatus(ctx.UserID()); err == nil {
out.LotteryDrawn = st.Drawn
out.LotteryPoints = st.Points
out.LotteryCost = st.Cost
}
}
if d.Post != nil {
if items, err := d.Post.HotPosts(rightAsideHotLimit); err == nil {
for _, it := range items {

View File

@@ -226,11 +226,22 @@ func (d Deps) ProfileAvatarPost(c *gin.Context) {
// ProfileCheckInPost 每日签到PRG
func (d Deps) ProfileCheckInPost(c *gin.Context) {
ctx := d.ctx(c)
redir := safePointsRedirect(c.PostForm("redirect"))
if !ctx.CheckCSRF() {
if redir != "/profile" {
ctx.SetFlash("无效请求,请重试")
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, "无效请求,请重试")
return
}
if d.Points == nil {
if redir != "/profile" {
ctx.SetFlash("积分服务未就绪")
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, "积分服务未就绪")
return
}
@@ -238,24 +249,40 @@ func (d Deps) ProfileCheckInPost(c *gin.Context) {
if err != nil {
if errors.Is(err, services.ErrAlreadyCheckedIn) {
ctx.SetFlash("今日已签到")
ctx.Redirect("/profile")
ctx.Redirect(redir)
return
}
if redir != "/profile" {
ctx.SetFlash(err.Error())
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, err.Error())
return
}
ctx.SetFlash(fmt.Sprintf("签到成功:连续 %d 天,获得 %d 积分", st.Streak, st.TodayPoints))
ctx.Redirect("/profile")
ctx.Redirect(redir)
}
// ProfileLotteryPost 每日抽奖PRG
func (d Deps) ProfileLotteryPost(c *gin.Context) {
ctx := d.ctx(c)
redir := safePointsRedirect(c.PostForm("redirect"))
if !ctx.CheckCSRF() {
if redir != "/profile" {
ctx.SetFlash("无效请求,请重试")
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, "无效请求,请重试")
return
}
if d.Points == nil {
if redir != "/profile" {
ctx.SetFlash("积分服务未就绪")
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, "积分服务未就绪")
return
}
@@ -263,7 +290,12 @@ func (d Deps) ProfileLotteryPost(c *gin.Context) {
if err != nil {
if errors.Is(err, services.ErrAlreadyLottery) {
ctx.SetFlash("今日已抽奖")
ctx.Redirect("/profile")
ctx.Redirect(redir)
return
}
if redir != "/profile" {
ctx.SetFlash(err.Error())
ctx.Redirect(redir)
return
}
d.renderProfile(ctx, err.Error())
@@ -274,5 +306,23 @@ func (d Deps) ProfileLotteryPost(c *gin.Context) {
} else {
ctx.SetFlash("抽奖结果:未中奖,明天再来")
}
ctx.Redirect("/profile")
ctx.Redirect(redir)
}
// safePointsRedirect 签到/抽奖 PRG 回跳:仅允许 /、/board/…、/post/…;空或非法则 /profile
func safePointsRedirect(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" || strings.Contains(raw, "://") || strings.HasPrefix(raw, "//") || strings.Contains(raw, "..") {
return "/profile"
}
if raw == "/" {
return raw
}
if strings.HasPrefix(raw, "/board/") || strings.HasPrefix(raw, "/post/") {
if strings.ContainsAny(raw, "?#") {
return "/profile"
}
return raw
}
return "/profile"
}