Files
chiyiandClaude Opus 4.8 8ec4562f81 feat: 易支付积分充值 + 站内公告 + OpenAI 视频修复 + grok/adobe 失败处理
充值/订单(易支付 mapi):
- 订单表 + 30 分钟自动取消;支付弹窗(二维码/跳转监控、倒计时、轮询、5s 倒计时关闭)
- 系统设置可配:开关/商户ID/密钥/支付地址(根地址拼 /mapi)/支付方式/最低额/积分比例(默认 1元=100积分)
- 异步通知 MD5 验签、幂等到账;用户累计充值;前台/后台订单页(筛选+搜索+分页,前后台分风格);用户管理累计充值列

站内公告:
- Markdown 公告,登录用户首次访问/刷新弹出;内容哈希做版本,改了就重新推;管理员不弹;空内容=下线

OpenAI 视频(/v1/videos)修复:
- /content 拿不到视频:grok 资源 URL 需鉴权,改为用生成账号 token 取流;adobe/runway 公开 URL 直代理(不存 RustFS)
- size→分辨率用短边判定(1280x720 = 720p,之前误判 1080p 被拒)

失败处理:
- grok 429「Too many requests」/403 anti-bot 改判临时错误(不再误封号),真额度耗尽才算 quota
- adobe 视频 408 / system under load 归为临时错误 → tempAsDead 封号

其它:
- 充值默认关闭;签到格子浅色可见;登录验证码按钮浅色可读;并发/账户信息展示
- 创作记录/画图台只显示画图台作品(排除 API);日志页 API 视频预览显示 —
- 视频去画中画/下载/投屏(全局);图片缩略图改背景图规避 Edge 视觉搜索
- 订单/兑换码/配置/日志菜单文案与图标;充值版块样式

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 01:32:33 +08:00

63 lines
2.1 KiB
Go

package service
import (
"context"
"crypto/sha256"
"encoding/hex"
"strings"
"backend/internal/model"
"backend/internal/repo"
)
// AnnouncementService serves the site-wide 公告 (markdown). It tracks, per user,
// the version they last dismissed so an updated announcement re-pops for everyone
// who hasn't seen the new text. The "version" is a hash of the content, so saving
// identical text doesn't re-notify; any edit does.
type AnnouncementService struct {
settings *repo.SiteSettingRepository
users *repo.UserRepository
}
func NewAnnouncementService(settings *repo.SiteSettingRepository, users *repo.UserRepository) *AnnouncementService {
return &AnnouncementService{settings: settings, users: users}
}
func announcementVersion(content string) string {
if strings.TrimSpace(content) == "" {
return ""
}
sum := sha256.Sum256([]byte(strings.TrimSpace(content)))
return hex.EncodeToString(sum[:8]) // 16 hex chars — plenty to detect a change
}
// Content returns the raw markdown (admin editor).
func (s *AnnouncementService) Content(ctx context.Context) string {
v, _ := s.settings.GetValue(ctx, "announcement.content")
return v
}
// ForUser returns {content, version, seen} for the logged-in user.
func (s *AnnouncementService) ForUser(ctx context.Context, user *model.User) map[string]any {
content := s.Content(ctx)
version := announcementVersion(content)
// Admins never get the popup — they author/preview it in 系统设置 instead.
seen := version == "" || (user != nil && (user.Role == "admin" || user.AnnouncementSeen == version))
return map[string]any{
"content": content,
"version": version,
"seen": seen,
}
}
// MarkSeen records that the user has dismissed the given version.
func (s *AnnouncementService) MarkSeen(ctx context.Context, userID, version string) error {
_, err := s.users.Update(ctx, userID, map[string]any{"announcement_seen": version})
return err
}
// Save persists new announcement markdown (admin). An empty string clears it.
func (s *AnnouncementService) Save(ctx context.Context, content string) error {
return s.settings.UpsertValue(ctx, "announcement.content", content)
}