2f672920d9
feat:增加实际使用量
2026-07-30 15:34:50 +08:00
81ee22d2ee
feat:修改样式
2026-07-30 15:07:56 +08:00
b0357d39a4
feat: 修改git规则
2026-07-29 16:12:36 +08:00
a63055a5d3
chore: lock .claude/ via nested .gitignore fallback, dedupe root rule
2026-07-29 16:10:40 +08:00
d5a08db56c
feat:添加存储空间
2026-07-29 16:03:33 +08:00
zerosaturation
128a12fa03
fix(asset-detail): fade in sibling modules during up-pull collapse
...
The .card-siblings wrapper lives inside <scroll-view v-else>, so when
viewMode flips to 'expanded' the wrapper is destroyed and the
siblingFadeOpacity binding has no element to animate. Up-pull therefore
shows nothing fading in even though the computed correctly returns
0 -> 1.
Add a top-level overlay that mirrors the same four modules
(card-meta-row, info-row, creator-section, chain-section) and binds
its opacity to siblingFadeOpacity. The overlay is rendered only when
viewMode === 'expanded', positioned fixed at the bottom with z-index 60
(above the expanded card at 50, below the header at 100), and uses
pointer-events: none on the wrapper so the up-pull gesture from the
lower viewport can still reach .pull-collapse-surface; interactive
children re-enable pointer-events: auto.
The original .card-siblings inside <scroll-view> is untouched and
still drives the down-pull fade-out.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 15:56:45 +08:00
zerosaturation
3d984f19cd
feat(asset-detail): pull-down/up card gestures with smooth follow
...
Add AssetCardPullExpand component for locked-state display and wire
up/down gesture symmetry on the asset detail page.
Down-pull at top of list:
- card scales 1.0 -> 1.6 with translateY follow, sibling modules fade
out, hint text '下拉查看大图' -> '松手查看大图' past 80rpx
- on release past threshold, viewMode flips to expanded
Up-pull in expanded state:
- card shrinks 1.6 -> 1.0 with translateY follow, sibling modules fade
back in, hint text '上拉收起' -> '松手收起' past 80rpx
- on release past threshold, viewMode flips back to normal
- the locked scale(1.6) lives on the wrapper so the release animation
stays single-jump, not dual-jump
Implementation:
- AssetCardPullExpand renders the card at scale(1) and exposes isExpanded
for the parent's locked-state toggle (showMask prop gates the legacy
full-screen tap-to-collapse overlay)
- both gesture surfaces use lazy startY capture in onMove to avoid
tap-induced jumps; no CSS transition on the wrapper during active pull
- 0.15s transform transition smooths the release interpolation only
- header bar and report/share buttons stay visible in both states;
v-if/v-else-if/v-else chain (loading -> error -> expanded -> scroll-view)
preserved; existing 8 card class names and parent CSS untouched
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 14:13:39 +08:00
zerosaturation
27313f414a
feat(profile): render level progress bar in LV box
...
profile.vue only rendered fan level digit, hiding the fact that
fans level up via accumulated exhibition hours (6h/level, cap 20),
not login/task experience. Users saw "LV X" without "X more
hours to next level", so upgrades felt invisible.
- backend: surface exhibition_hours / next_level_hours on
CurrentIdentityDTO via a new loadLevelProgress helper in
user_controller (read-only, no proto regen, no service
surface change). Full-level responses set next_level_hours =
exhibition_hours so the client can derive progress=1.
- frontend: split level-box into base track + gold fill + text
overlay; width bound to progressRatio with 0.4s transition.
Adds "距 LV X+1 还差 N 小时" hint and MAX indicator for
full-level users.
2026-07-27 18:02:59 +08:00
zerosaturation
83999995f5
refactor(frontend): consolidate user storage read via getStoredUser helper
...
uni.getStorageSync('user') 在 key 从未写过的 UniApp 平台下默认返回 ""(空串)
而非 null/undefined,旧式 `JSON.parse(uni.getStorageSync('user')) || {}` 在
parse 阶段直接抛 `SyntaxError: Unexpected end of JSON input`,导致 quickLogin
setup 崩溃,并触发 Vue 二次告警 `Invalid vnode type: undefined`。
新增 utils/getStoredUser.js 单点封装,统一处理:
- 空值/损坏 JSON 兜底(返回 null,不抛)
- 与 store/modules/user.js 初始化块同源守卫
- 失败 warn 级别日志,不影响业务
替换全库 16 处不安全 `JSON.parse(userStr)` 调用:
composables/useLaserMint.js composables/useShare.js
utils/guideConfig.js utils/preloadApi/core.js
pages/castlove/lenticular/lenticular-result.vue
pages/support-activity/index.vue pages/support-activity/components/{TopRanking,ActionBar}.vue
pages/components/{Header,ShareModal,ShareReportButtons}.vue
pages/tasks/{GuideModal,daily-tasks}.vue
pages/asset-detail/asset-detail.vue pages/exhibition/exhibition.vue
pages/profile/profile.vue pages/login/quickLogin.vue(原崩点)
净 -18 行,17 文件改动 + 1 文件新增。后续如需支持多 key(如 'adminUser'),
把 getStoredUser 扩成泛型 getStoredJSON(key) 即可。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:16:49 +08:00
zerosaturation
d6573efcdd
fix(frontend): align daily_login storage cache with backend task_completed
...
Header.vue 的 '日内去重' 逻辑原本在 reportEvent 走 .then() 时无条
件 setStorageSync(true),但 reportEvent 在后端 task_completed=false
时仍 HTTP 200 + code=0(例如 trigger_event=NULL / 任务不存在等),
.then() 照样跑,storage 缓存被错误标记为'已报',后续进 home 永远跳
过 reportEvent,daily_login 任务永远无法补完成(死锁)。
修后 .then((res) => ...) 按后端真实 task_completed 判定:
- task_completed=true → setStorageSync + 清昨日(保持原优化)
- task_completed=false → removeStorageSync(自愈,允许下次重试)
影响范围:仅 daily_login。其他 daily 任务(daily_browse_asset /
daily_place_asset)本来就没前端 storage cache,不受影响。
当前 daily_login 卡住的用户:手动清 storage 后重进 home 可触发自愈;
或等明天 5 点跨天后 storage key 自动失效(同日 5 点重置周期)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:45:49 +08:00
zerosaturation
b17a3b4b4a
chore(asset-detail): silence debug console.log in loadData
...
调试期临时加的 [asset-detail] loaded asset 日志已无用途,清掉避免
生产环境输出噪音。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:37:54 +08:00
zerosaturation
ab1810e433
fix(frontend): clear all daily_login_completed_* keys on logout
...
登出时清理 daily_login 本地缓存的 key 写错:实际清的是
daily_login_completed_${today},但 Header.vue 写入的 key 是
daily_login_completed_${date}_${uid}_${starId},格式不匹配导致
根本没清掉任何东西,跨账号/重装场景会留旧去重残留,新一天本应触发
的 daily_login 上报被本地 cache 误判成'已报'。
改成模糊匹配所有 daily_login_completed_*(uni 没有'按前缀删'接口,
getStorageInfoSync + filter + 循环 remove,模式同
utils/preloadApi/storage.js#clearForUser)。
注:daily_login 仍保留前端去重(spec §3 要求);其他 daily 任务
(daily_browse_asset / daily_place_asset) 本来就没前端去重,完全
靠后端 ProcessTaskEvent 幂等(status=completed/claimed 跳过)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:36:58 +08:00
zerosaturation
89d5d12450
fix(task): backfill id=1/2 trigger_event + cleanup misleading NULL comments
...
daily_browse_asset (id=2) / daily_login (id=1) 之前永远不触发,根因:
2026_07_21_004 迁移只 backfill 了 id=3/4 的 trigger_event=task_key,
id=1/2 留 NULL。但 ProcessTaskEvent 引擎按 trigger_event = ? 严格匹配
(spec §4 单一隔离单元),NULL 不会命中,前端 reportEvent 后引擎跑空
循环、daily-tasks 任务永远 pending。
修复:
- 新增 2026_07_27_005 迁移:回填 id=1/2 的 trigger_event=task_key(幂等)
- 修订原 004 迁移的错误注释('trigger_event 留 NULL'误导后人)
- model/task_models.go:删除 'NULL 仍走 def.TaskKey 兜底' 不实承诺
- impl plan §37/39:修订错描述('暂不依赖 trigger_event 列')
- docker/init-db.sql:124668/124671:重建 dev DB 时也带 trigger_event
- daily_task_repo_test.go:补 2 个回归测试
- NullTriggerEvent_NotMatched:trigger_event=NULL 时查不到
- TriggerEventEqualsTaskKey_Matches:trigger_event=task_key 时能命中
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:36:40 +08:00
zerosaturation
4284775ed6
docs(daily-task): 修订设计 spec + 新增实施计划
...
- specs/2026-07-21-daily-task-config-driven-design.md: 修订 8 处反映实施反馈
(frontend 入口迁移、TaskEventPayload 路径、emit 失败仅 Warn 等)
- plans/2026-07-21-daily-task-config-driven-impl.md: 新增 8 阶段实施计划
(A schema → B 事件目录 → C 完成引擎 → D 事件接入 → E 重置 → F 测试 → G 部署 → H 验收)
时序与 spec §4 子章节对齐
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:47:24 +08:00
zerosaturation
5ab1a537db
feat(frontend): daily-task reportEvent 接入 + exhibition 错位实现迁移
...
Phase F.2 前端接入:
- asset-detail.vue: onLoad 触发 daily_browse_asset reportEvent(后端幂等:
status=completed/claimed 时 skip;本地不缓存避免跨天去重逻辑复杂)
- exhibition.vue: 删除原 exhibition 页 onLoad 的 daily_browse_asset reportEvent
调用(错位实现,浏览发生在详情页不在展厅页)
- myWorks.vue: handleAssetSelect 成功分支新增 daily_place_asset reportEvent
- daily-tasks.vue: 移除多余的 task-progress 文本显示(接口不返回
current_count/target_count,前端 UI 也不展示)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:47:01 +08:00
zerosaturation
cc7794e0f4
test(task): consumer + daily_task repo/service + like_bet ON CONFLICT 修订
...
Phase F.6 测试覆盖:
- mq/consumer_test.go: MQ consumer 单元测试,用 fakeDailyTaskService 模拟
ProcessTaskEvent(newHandleTaskEvent 唯一调用点)
- repository/daily_task_repo_test.go: DailyTaskRepository CRUD 单测
(复用 like_bet_repo_test.go 的 TestMain + dev PG 15432 sentinel 模式)
- service/daily_task_service_test.go: DailyTaskService.ProcessTaskEvent 核心
完成判定逻辑单测
- repository/like_bet_repo_test.go: UniqueConstraint 测试语义修订 —
BatchCreate 内部用 clause.OnConflict{DoNothing: true} 实现幂等,
重复 (exhibition_id, like_id) 不再报错,DB 仍保留 1 条记录
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:46:29 +08:00
zerosaturation
a9ce281d5e
feat(asset): emit task:event on mint success (fix daily_mint 悬空)
...
assetService 接入 MQ producer:铸造成功时 emit `task:event { event_type: daily_mint }`,
由 taskService consumer 异步调 ProcessTaskEvent 完成判定。
- main.go: 初始化 MQ (Asynq producer-only),沿用 galleryService 模式;
失败仅 Warn 不阻塞主路径
- service/mint_service.go: CreateMintOrder 成功分支 fire-and-forget emit
- mq/producer.go: 新增 EnqueueTaskEvent helper,封装 TaskEventPayload marshal +
adapter.TaskProducer.Enqueue
配套:spec §4 / Phase F.1 / impl plan Phase D。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:45:42 +08:00
zerosaturation
1e8a7476e0
feat(task): register task:event MQ consumer
...
- consumer.go: +newHandleTaskEvent(dailySvc) adapter.TaskHandler
- unmarshal TaskEventPayload -> delegate to ProcessTaskEvent
- returns non-nil error on failure -> Asynq MaxRetry=3
- consumer.go: RegisterHandlers signature +dailySvc parameter
- consumer.go: register TaskEvent with QueueDefault, MaxRetry=3
- main.go: pass dailySvc to taskmq.RegisterHandlers
Now task:event MQ messages produced by:
- assetService/mq/producer.go (Phase F.1) for daily_mint
- frontend reportEvent RPC -> gateway (Phase F.2 callers) for daily_login/browse_asset/place_asset
are consumed asynchronously and routed to ProcessTaskEvent.
spec: docs/superpowers/specs/2026-07-21-daily-task-config-driven-design.md §3 F3 + §4.2
plan: docs/superpowers/plans/2026-07-21-daily-task-config-driven-impl.md Phase E
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:42:10 +08:00
zerosaturation
ecdd96da5b
feat(mq): task:event task type + business event constants
...
- pkg/mq/tasks/task_events.go (new): business event constants
- EventDailyLogin, EventDailyBrowseAsset, EventDailyMint, EventDailyPlaceAsset
- TaskEventPayload{UserID, StarID, EventType}
- pkg/mq/tasks/registry.go: +TypeTaskEvent = 'task:event'
Spec §3 F3 separation: business event constants live in task_events.go;
MQ task type strings live in registry.go (revenue:/gallery:/* style).
Consumed by:
- assetService/mq/producer.go (Phase F.1 emitter for daily_mint)
- frontend pages (Phase F.2 callers via task-api.js reportEvent -> gateway -> task:event)
- taskService/mq/consumer.go (Phase E consumer -> ProcessTaskEvent)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:41:49 +08:00
zerosaturation
e2f1122cc8
feat(task): GORM model + repository + ProcessTaskEvent engine
...
Phase B (model + repository):
- TaskDefinition: +TriggerEvent string, +TargetCount int
- UserDailyTaskProgress: +Progress int
- DailyTaskRepository: ListActiveDailyTaskDefinitions signature -> (starID, eventType string)
- IncrementProgress: atomic UPDATE progress=progress+1 WHERE status='pending'
(returns rows-affected 0 + re-read on race; caller decides completion transition)
- ResetAllDailyTasks: +progress: 0 in Updates map
- InitDailyTasksForUser: pass eventType='' (backward-compat)
Phase D (engine + ReportEvent delegation, spec §4.3):
- TaskEventResult struct (CompletedTaskKeys []string)
- DailyTaskService.ProcessTaskEvent: 5-step engine
1. ListActiveDailyTaskDefinitions(starID, eventType) - filter by trigger_event
2. GetOrCreateDailyProgress; skip if completed/claimed (day idempotency)
3. IncrementProgress (atomic +1)
4. if progress >= target_count -> status='completed'
5. UpdateDailyProgress + accumulate CompletedTaskKeys
- ReportEvent rewired: delegates to ProcessTaskEvent, maps TaskEventResult
to ReportEventResponse (F1: single isolation unit; MQ consumer + RPC both use engine)
- 3 callers (GetDailyTasks / ClaimDailyTask / ClaimAllDailyTasks) pass eventType=''
for backward-compat (preserves daily_login / daily_browse_asset before Phase F)
Note: Phase B and D are bundled because the signature change in B is what enables
D's engine to filter by trigger_event. Splitting would leave the codebase
uncompilable in the interim.
spec: docs/superpowers/specs/2026-07-21-daily-task-config-driven-design.md §4
plan: docs/superpowers/plans/2026-07-21-daily-task-config-driven-impl.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:41:35 +08:00
zerosaturation
bdf3abc2bc
feat(task): migration + backfill daily_task trigger_event
...
- task_definitions: add trigger_event varchar(64) + target_count int default 1
- user_daily_task_progress: add progress int default 0
- backfill id=3 (daily_mint) + id=4 (daily_place_asset) trigger_event
- sequence sync per CLAUDE.md (task_definitions_id_seq + user_daily_task_progress_id_seq)
- sync docker/init-db.sql CREATE TABLE + COPY data (id=1,2,5 leave NULL; id=3,4 fill)
spec: docs/superpowers/specs/2026-07-21-daily-task-config-driven-design.md
plan: docs/superpowers/plans/2026-07-21-daily-task-config-driven-impl.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:38:46 +08:00
zerosaturation
907e2265b1
feat:修改铸造消耗弹窗样式
2026-07-27 13:38:23 +08:00
ea39ee1881
feat:修改图片尺寸和uni配置
2026-07-27 12:48:02 +08:00
zerosaturation
383b5e042e
docs(starbook): add has_more design spec and implementation plan
...
记录星册首页 More 按钮恢复的方案说明与 TDD 实施计划,作为本次特性 commit e079a6c 的设计依据。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 19:20:32 +08:00
zerosaturation
e079a6c2e2
feat(asset): restore starbook home More button by Asset.LikeCount ordering
...
starbook 首页每个 grade/group 按 Asset.LikeCount DESC, Asset.ID ASC 取前三张,超三显示 has_more;
GetAssetsByType 不截断返回全部匹配项。包含:
- proto/asset.proto 声明 GetAssetsByType RPC + 消息,重新生成 .pb.go/.triple.go
- gateway starbook controller 改用标准 AssetService 客户端
- assetService: 三个 build*GroupForAssets 增加 previewLimit 参数
- 新增 sortAndLimitAssetItems 共享助手
- 新增 asset_service_group_test.go DB-free 单测
- 新增 TestStarbookHomePreviewAndMoreUseAssetLikeRanking 端到端回归
- frontend pages/starbook/items.vue 适配嵌套 groups/grades/items 响应,单 grade 竖向展示
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 19:20:16 +08:00
zerosaturation
23e5a600ad
fix(0a): replace real keys with placeholders in .env.example + harden .gitignore
...
- .env.example: 3 处真 key 替换为 <REPLACE_ME_*> 占位
- L104 DIFY_API_KEY (原 app-tIfFhFwj3xnbRurK1oxxBXnA, 注释内也含)
- L117 OPENAI_API_KEY (原 sk-proj-srKxyb... 完整 164 字符)
- L135 OPENAI_API_KEY (原 sk-eIOujD5rUugIRIPecFi3I2rFr6...)
- L142 DIFY_API_KEY (原 app-aHnBfMeOQp7A9dQneIFPdPaZ)
生产真值由环境变量注入(backend/.env / docker/.env / K8s Secret);
- .gitignore: 加严真 .env 排除
- backend/.env / backend/.env.local / backend/.env.production / *.local
- docker/.env / docker/.env.local / docker/.env.prod / *.local
- backend/services/*/.env
防 0b 阶段(运维轮换)完成前有人误提交真值文件;
- 自检: grep -E 'sk-proj|sk-eIOu|app-[A-Za-z0-9]{20,}' backend/.env.example = 0 真 key
- 后续: 0b 阶段(运维真轮换)完成后, 跑 .superpowers/sdd/batch0-filter-repo-commands.md
列出的 git filter-repo 命令清历史(用户 review 后执行)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 15:23:26 +08:00
zerosaturation
4a7b527759
fix(k8s): livenessProbe port to healthPort + values.yaml healthPort +1000 (follow-up P2)
...
- 11 个 deployment 模板: livenessProbe + readinessProbe 改用 {{ $svc.healthPort | default $svc.port }}
(此前用 $svc.port 指向 Dubbo 端口而非 health 端口, 探针 100% fail, Pod 永不就绪);
- values.yaml: 10 个 service 的 healthPort 从 =port 改为 =port+1000 (21000/21002/.../21011);
statisticService 21009 一致(巧合值, 不冲突);
- gateway 无 healthPort 字段, 走 default fallback 仍为 8080 (HTTP 端口,正确);
- 真正修复: 11 个 service 探针现都指向实际 health server 监听的 port+1000;
- 注意: 注释 # healthPort — 默认同 port; moderationservice 例外 与新事实不符 (留 follow-up 改注释)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 15:14:38 +08:00
zerosaturation
812aab2b79
fix(moderationService): start health server on port+1000 (follow-up P1)
...
- moderationService main.go 加 health.NewHandler + Start/Stop, 与其它 9 个 service 一致;
- 启动 health server 监听 21011 (Dubbo 端口 20011 + 1000), /health + /healthz 都注册;
- 补完后 11 个 service 都启了 health server(注: statisticService 用 gin 21009, 不冲突);
- 此前 P1 follow-up 标记的 'Pod never ready' 阻塞解除 (前提: k8s 探针改用 healthPort, 见下个 commit)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 15:14:31 +08:00
zerosaturation
d3b57e3058
docs: add starbook-recovery implementation plan (2026-07-24)
...
- 5 Task plan: proto 加 GetAssetsByType RPC (只改 .pb.go 不动 .triple.go);
- assetService provider 实现新 handler;
- gateway 重建 starbook_controller.go 薄壳(接口零变, owner→assetService);
- 路由注册 + main.go 装配;
- 端到端验证;
- 由于 Task 2 误删,本 plan 完整回滚 + 重建,前端 4 处调用零修改。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:05:01 +08:00
zerosaturation
3cce70ec2e
chore(scripts): create_gallery_test_users.go add setval + dev.sh starbook cleanup
...
- create_gallery_test_users.go 末尾循环 setval 序列重置 (config 4.3-C):
user/asset/booth_slots; exhibitions 移除 (无硬编码 id + 序列不一定存在);
booth_slots PK=slot_id 用 MAX(slot_id) 正确列名;
- dev.sh: 移除 starbookService 引用 (config 4.2 残留)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:04:45 +08:00
zerosaturation
e1fb138e8e
fix(deploy): health probe paths + starbookService deployment removal (config 4.3)
...
- k8s helm values.yaml: notificationService/moderationService healthPath 改 /health
(原 /healthz 与 pkg/health 实际注册路径不一致);
- 删除 k8s helm starbookservice template (服务已删, 部署已无效);
- docker-compose.{local,prod}.yml: 删 starbookService 服务块 + URL env + depends_on;
- Dockerfile.services: 删 starbookService 构建 Stage;
- build.sh + deploy.sh: 删 starbookService 引用 + ALL_SERVICES_NAME 帮助文本。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:04:41 +08:00
zerosaturation
f46464fdf9
chore(backend): remove starbookService + proto (config 4.2 cleanup)
...
- 删 services/starbookService 目录 (Phase 2 决策, owner 已迁 assetService);
- 删 backend/proto/starbook.proto (proto 源, 不再被任何 service 引用)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:04:37 +08:00
zerosaturation
7ff2d5cdc4
fix(gateway): restore /api/v1/starbook/* via assetService (recovery from Task 2 over-deletion)
...
- 恢复 /api/v1/starbook/home + /api/v1/starbook/items 网关路由
(owner 从被删的 starbookService 切到 assetService);
- assetService 加 GetAssetsByType RPC (type/category/grade 过滤), 仅改 asset.pb.go (不动 triple.go);
- gateway 新建 starbook_controller.go 薄壳 (auth + 参数解析 + RPC);
使用 Task 1 扩展接口 AssetServiceGetAssetsTypeClient (零装配增量, 复用现有 assetClient);
- 响应结构与原端点完全一致 (前端 4 处调用零修改, 实测 home 2514 bytes JSON shape 一致);
- items 支持 type/category/grade/page/page_size 完整参数;
- 路由注入到 AuthMiddleware 保护的 v1 group;
- 5/5 starbook 测试 + 完整 workspace build/vet 通过。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:04:01 +08:00
zerosaturation
8a767fb400
fix(backend): service stability — batch 3 accumulated (bcrypt off-txn / login anti-enum / MQ stub / aiChat / event reliability / gateway aggregate)
...
- 3.1 bcrypt 移出事务 (Register): repository.HashPassword 前移到 db.Transaction 之前。
- 3.2 Login 消除用户枚举 + 限流 + timing 抹平: pkg/errors 加 ErrInvalidCredential / ErrTooManyLoginAttempts;
user-not-found 跑 dummy bcrypt 抹平 ~100ms 时序差; mobile 5次/ip 20次 per 15min 限流 (Redis, fail-open 降级)。
- 3.3 MQ streams adapter 停用 → stub: 0 业务调用方, noop EventProducer.Publish; Init 不再装配 streams;
11 处硬编码 'gallery'/'default' 抽常量到 pkg/queue/consts (值不变, 消漂移)。
- 3.5 JWT 密钥治理: pkg/jwt MustInit fail-fast + atomic.Value, 50-goroutine race_test 零告警;
MustInit 调用点 gateway main + auth_provider + loadgen 同步更新。
- 3.6 aiChat 健壮性: SaveContext 用 persona.ID(非 req.PersonaId); Redis/memory 错误 记 WARN 不静默;
Dify err 映射稳定用户文案。
- 3.7 statistic.Client 重构: TrackEvent 改 buffered channel (cap 1024) + dispatchLoop worker。
- 3.8 网关聚合: StarCache (60s TTL, singleflight) 替换 GetFanIdentities 链式调用;
DeleteAccount 改网关直调 userService.DeleteAccount(避免改 hand-written triple.go);
铸造双写改异步 channel+consumer (3 retry)。
- 大量单测: 各子项 TDD (RED→GREEN), 关键并发 race_test (50 goroutine)。
- .env.example JWT_SECRET 改为 ≥32 字节 base64 示例(原为空, 被 MustInit 立即拒)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:03:21 +08:00
zerosaturation
a337f43f86
fix(config): port alignment + service test doubles (batch 4.1/4.5)
...
- gateway/config Dubbo URL 默认端口修正(原 20004/20005 与服务实际 20001/20004 不符);
- userService main.go 硬编码 WithPort(20000) 修死码, -port flag / PORT env 真正生效;
- .env.example 11 个 DUBBO URL 同步(对齐 serviceDefaultPorts map + 实际服务 bind);
- 删除 DUBBO_STARBOOK_SERVICE_URL 死引用(starbookService 已在 batch 4.2 删除);
- .env.example Service Ports 注释块补全 10 行, 与网关 Dubbo URL + port_test 三方一致;
- 10 个 service 各加 port_test.go, 断言 flag 默认与 serviceDefaultPorts map 一致;
- 11 个 port_test 总计 13 测试覆盖(网关 + 10 service);
- .gitignore 加 userService/main.go.bak 排除(临时备份)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:02:14 +08:00
zerosaturation
9d538251b6
chore(gitignore): exclude userService/main.go.bak
...
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-24 14:02:07 +08:00
zerosaturation
e1326acaf9
fix(backend): service stability — bcrypt off-txn / login anti-enum / MQ stub / aiChat / event reliability / gateway aggregate (batch 3)
...
- 3.1 bcrypt 移出事务 (Register): repository.HashPassword 前移到 db.Transaction 之前,消除连接池占用。
- 3.2 Login 消除用户枚举 + 限流 + timing 抹平: pkg/errors 加 ErrInvalidCredential
/ErrTooManyLoginAttempts; 用户不存在/密码错/密码空 三路径统一返回同一错误;
mobile 5次/ip 20次 per 15min 限流 (Redis, fail-open 降级); user-not-found 走
dummy bcrypt 抹平 ~100ms 时序差,完全消除枚举侧信道;空密码分支已核实无时序 leak。
- 3.3 MQ streams adapter 停用 → stub: 0 业务调用方, 新 stub EventProducer.Publish no-op;
pkg/mq/mq.go Init 不再装配 streams; 全仓 grep 验证 11 处硬编码
'gallery'/'default' 集中到 pkg/queue/consts (值不变, 仅消漂移)。
- 3.5 JWT 密钥治理: pkg/jwt MustInit fail-fast + atomic.Value (见上一个 commit 293c7b1 )。
- 3.6 aiChat 健壮性: SaveContext 用 persona.ID(非 req.PersonaId); Redis/memory 错误
记 WARN 不静默; Dify err 映射稳定用户文案,原始 err 仅服务端日志。
- 3.7 statistic.Client 重构: TrackEvent 改 buffered channel (cap 1024) + dispatchLoop
worker; 失败 ERROR 日志带字段; drop 记 WARN; Close 可重复调用。
- 3.8 网关聚合: StarCache (60s TTL, singleflight) 替换 5+ 处 GetFanIdentities 链式调用;
DeleteAccount 改网关直调 userService.DeleteAccount(避免改 hand-written triple.go
风险,见报告 §5 proto 风险复盘); 铸造双写改异步 channel+consumer (3 retry)。
- 大量单测: 各子项 TDD (RED→GREEN), 关键并发 race_test (50 goroutine)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 18:50:40 +08:00
zerosaturation
293c7b14ae
fix(security): JWT key governance — MustInit fail-fast + atomic.Value
...
- pkg/jwt: 删 public SetSecret; 加 MustInit(secret string) 启动时强制注入,
缺/为空/等于弱默认值时返回 error(运行期不可再改); 密钥用 atomic.Value
存 []byte,所有读走 mustSecret() 原子 Load,消除 SetSecret/ParseToken 并发
data race(go test -race 零告警)。
- gateway main + auth_provider: 启动时 MustInit 读 JWT_SECRET env,失败 fatal。
- scripts/loadgen/seed/tokens: 同步 MustInit。
- .env.example: JWT_SECRET 改为 ≥32 字节 base64 示例(原为空,被 MustInit
立即拒);注释提示生产 MUST replace。
- 测试: 4 个 MustInit 行为 + 1 个 50-goroutine race 覆盖。
- 行为变更: 任何 .env 缺 JWT_SECRET 或用占位 secret 的服务,启动会 panic
(这是 fail-fast 期望行为);其余 4 个 .env 文件占位由 ops 单独轮换。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 18:50:12 +08:00
zerosaturation
3407e30395
refactor(assetService): CreateMintOrder 3-phase txn + orphan reconciliation (P0-2)
...
- CreateMintOrder 拆三段: txn1(PENDING→PROCESSING,不调RPC/不写asset)
→ 事务外 UpdateCrystalBalance 扣水晶 → txn2(建asset+registry→SUCCESS);
消除 DB 事务内嵌跨服务 gRPC(连接池占用+跨服务事务风险)。
- 任一步失败 markMintOrderFailed 独立事务标 FAILED(不回滚已扣水晶=审计流水,
重试靠 Task1 source_id 幂等 + Task2 入口短路防双扣); nil-cause 防御。
- 孤儿订单对账 ReconcileStuckMintOrders: 扫陈旧 PROCESSING 单,查 mint_cost 流水—
已扣未建→FOR UPDATE 行锁下幂等补完 SUCCESS(串行化并发防双建),未扣→FAILED,绝不退款。
- main.go 接线周期 worker(MINT_RECONCILE_INTERVAL_SEC 默认600s,0关,优雅退出)。
- 测试: RPC失败落FAILED / 对账未扣→FAILED / 已扣未建→补完(含幂等二次跑)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 12:28:57 +08:00
zerosaturation
b7022d2dc1
fix(backend): auth boundary — trusted identity from ctx (batch 2)
...
- 新增 pkg/authctx: 从 Dubbo attachment/gRPC metadata 提取可信 user_id/star_id,
统一覆盖 req 同名字段, 缺身份返 Unauthenticated。
- 各 provider 接入(堵身份伪造/越权):
* moderation SubmitReport 等 6 RPC(举报人伪造)
* asset CheckAssetLike/GetAssetQrcode/TrackShare(点赞/分享归因伪造)
* social CheckFriendship(修 starID=0 隐私预言机)
* activity PurchaseItem/BatchPurchaseItem(水晶扣费伪造)等 5 RPC
* gallery/aiChat/task/notification 迁移 authctx, 删散落 extractUserInfo*
- social 正确性: GetUserLikedAssets OR 显式分组(防御); GetRandomUsersByStar 真随机(去 rand.Seed)。
- gateway: /auth/validate 移入 AuthMiddleware 保护组(/refresh 保留,依赖注入身份)。
- 删 userService 已迁移死函数; notification 缺身份错误码统一为 Unauthenticated。
- 各 provider 单测(伪造身份被覆盖 + 缺身份拒绝)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 01:12:35 +08:00
zerosaturation
878bd46399
fix(backend): financial correctness — settlement/hours/mint idempotency (batch 1)
...
- settlement(1.1): exhibition_revenue_records 加 UNIQUE(exhibition_id,cycle_start_time)
+ CreateRevenueRecord ON CONFLICT DO NOTHING; MQ 用 settled_at 替代复用 is_processed;
恢复扫描器过滤改 settled_at IS NULL; created_at 统一毫秒; 删死代码 cleanup_worker.go。
- hours(1.2): 新增 exhibition_hours_log/asset_exhibition_hours_log(source_id 唯一)幂等表;
fan_profile/assetLevel 的 AddExhibitionHours 按 sourceID 幂等(事务包裹); 存量重算脚本。
- mint(1.4/1.5/1.6): crystal_transaction_records (source_id,change_type) 部分唯一索引
+ UpdateCrystalBalance/CreateMintOrder 幂等; 保底改 crypto/rand; 下线伪 tx_hash;
doMint Redis Lua 原子限流。
- migrations 001/002/003; 各服务单测(自包含, 缺 DB t.Skip)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 01:11:54 +08:00
zerosaturation
d026102a91
docs: backend audit report 2026-07-21 (findings + corrections)
...
后端全面审查报告:微服务耦合/边界、配置、各服务正确性与安全问题分级(P0/P1/P2),
含数据库实测复核(§七 结算超发/累计时长)与多处复核更正(peripheral 密钥、social OR)。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 01:11:13 +08:00
zerosaturation
946493a0ff
fix:补齐appBUG
2026-07-22 16:29:39 +08:00
zerosaturation
d5103f7043
docs:新增后端bug修改文档
2026-07-21 21:14:22 +08:00
zerosaturation
b7f8f1b724
fix:修改登录页流程bug
2026-07-21 16:41:42 +08:00
zerosaturation
6f9b178fc1
fix:修改一键登录页到首页的bug
2026-07-21 15:00:49 +08:00
zerosaturation
cacc686e9a
fix:notification/moderation service healthcheck 配置错误
...
两个服务的 healthcheck 配置都有历史遗留 bug,导致 docker ps
一直显示 unhealthy / health: starting:
1) notificationService
- 现象: docker-compose healthcheck 路径写 /healthz,但 pkg/health
只注册 /health (backend/pkg/health/health.go:29),wget 永远 404
- 修复: compose 路径 /healthz -> /health,Dockerfile HEALTHCHECK
同样改 /health (双保险,rebuild 后不会回退)
2) moderationService
- 现象: main.go 没启 health server (只暴露 Dubbo Triple 协议),
compose 原 healthcheck 端口 20011 永远 timeout,
Dockerfile HEALTHCHECK 也指向 20011 同样错
- 修复: compose 用 healthcheck: test: ["NONE"] 显式覆盖 image
HEALTHCHECK,Dockerfile 改成 HEALTHCHECK NONE (image-level
也禁掉,rebuild 后不会回退)
gateway depends_on moderationservice: service_started (不依赖 healthy),
不影响入口可用性。健康状态靠 gateway 间接覆盖。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 12:12:46 +08:00
zerosaturation
7bf4666cdb
fix:restart 命令卡死问题
...
deploy.sh restart 命令原本 down && up --force-recreate + sleep 10,
在以下场景会卡死:
1) docker-compose up 中途异常退出,server-side 进程没回传 EOF,
本地 ssh 客户端没设 ServerAliveInterval,死链检测不到,
ssh 进程挂 25+ 分钟不退出 (2026-07-21 实测)
2) up --wait 等所有服务 healthy 才返回,但本 compose 里有
oss-cors-init (restart: no, exited(0) 永远不 running) +
moderationservice/notificationservice healthcheck 写错,
永远卡住
修改:
- ssh_cmd / ssh_cmd_batch / scp_cmd 加 ServerAliveInterval=30 +
ServerAliveCountMax=3,死链 90s 主动断
- restart 去掉前置 docker-compose down (up --force-recreate 自带 stop+create)
- restart 去掉末尾 sleep 10,改 polling gateway healthy (timeout 180s)
端到端实测 1:19 通过。
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 12:12:27 +08:00
zerosaturation
d554d7be83
feat:修改周边验证
2026-07-17 16:53:57 +08:00
zerosaturation
1f7faba9a8
feat:先关闭dify的使用
2026-07-16 11:35:07 +08:00