或就爱你功能
This commit is contained in:
parent
f5842f8702
commit
7b2b3a3e2f
@ -85,7 +85,7 @@ go run ./cmd/server -config configs/config.yaml -bootstrap
|
|||||||
5. 新增任务类型时,需要补齐创建接口、runner handler、重试/失败结果和产物策略。
|
5. 新增任务类型时,需要补齐创建接口、runner handler、重试/失败结果和产物策略。
|
||||||
6. 区域创建和区域启用/禁用是后台管理能力,实现在 `hyapp-admin-server`,不要为此新增 user-service RPC 或把后台开关逻辑下沉到 app 后端服务。
|
6. 区域创建和区域启用/禁用是后台管理能力,实现在 `hyapp-admin-server`,不要为此新增 user-service RPC 或把后台开关逻辑下沉到 app 后端服务。
|
||||||
7. 国家创建和国家启用/禁用是后台管理后端能力,实现在 `hyapp-admin-server`,不要为这些后台动作新增 user-service RPC 或 user-service 业务逻辑。
|
7. 国家创建和国家启用/禁用是后台管理后端能力,实现在 `hyapp-admin-server`,不要为这些后台动作新增 user-service RPC 或 user-service 业务逻辑。
|
||||||
8. 后台创建 BD Leader 必须传后台选择的 `regionId`;admin-server 负责权限和审计,user-service 在关系创建事务里更新目标用户 `users.region_id`,不要在 admin-server 里用 GetUser 做“用户原区域必须一致”的拒绝。
|
8. 后台创建 BD Leader 按目标用户当前 `users.region_id` 创建;admin-server 只负责权限和审计,不传 `regionId`,user-service 在关系创建事务里读取目标用户区域并写入 `bd_leader_profiles.region_id`,不要在创建 BD Leader 时顺手修改用户区域。
|
||||||
9. `hyapp.local/api` 是唯一允许直接依赖的 app 后端契约 module;不要 import `hyapp/services/*`、`hyapp/internal/*` 或 `hyapp/pkg/*`。
|
9. `hyapp.local/api` 是唯一允许直接依赖的 app 后端契约 module;不要 import `hyapp/services/*`、`hyapp/internal/*` 或 `hyapp/pkg/*`。
|
||||||
|
|
||||||
## 验证
|
## 验证
|
||||||
|
|||||||
@ -143,7 +143,12 @@ func (h *Handler) SetBDStatus(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bdRoleFromStatusRoute(c *gin.Context) (string, string) {
|
func bdRoleFromStatusRoute(c *gin.Context) (string, string) {
|
||||||
if strings.HasPrefix(c.FullPath(), "/admin/bd-leaders/") {
|
return bdRoleFromStatusPath(c.FullPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
func bdRoleFromStatusPath(fullPath string) (string, string) {
|
||||||
|
// FullPath 带有外层 /api/v1 分组;只要命中 BD Leader 状态路由片段,就必须走独立的 bd_leader_profiles,避免误查普通 bd_profiles。
|
||||||
|
if strings.Contains(fullPath, "/admin/bd-leaders/") {
|
||||||
return "bd_leader", "bd_leader_profiles"
|
return "bd_leader", "bd_leader_profiles"
|
||||||
}
|
}
|
||||||
return "bd", "bd_profiles"
|
return "bd", "bd_profiles"
|
||||||
|
|||||||
@ -12,6 +12,19 @@ func TestNormalizeListQueryAllowsCoinSellerComputedSorts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix 锁定线上真实 FullPath 带 /api/v1 前缀时,BD Leader 状态仍然写独立负责人表。
|
||||||
|
func TestBDRoleFromStatusPathUsesLeaderTableWithAPIPrefix(t *testing.T) {
|
||||||
|
role, target := bdRoleFromStatusPath("/api/v1/admin/bd-leaders/:user_id/status")
|
||||||
|
if role != "bd_leader" || target != "bd_leader_profiles" {
|
||||||
|
t.Fatalf("leader status route must use bd_leader_profiles, got role=%q target=%q", role, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
role, target = bdRoleFromStatusPath("/api/v1/admin/bds/:user_id/status")
|
||||||
|
if role != "bd" || target != "bd_profiles" {
|
||||||
|
t.Fatalf("bd status route must use bd_profiles, got role=%q target=%q", role, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSortCoinSellerListItemsComputedFields 验证 wallet 聚合字段排序只改变展示顺序,不改动币商身份数据。
|
// TestSortCoinSellerListItemsComputedFields 验证 wallet 聚合字段排序只改变展示顺序,不改动币商身份数据。
|
||||||
func TestSortCoinSellerListItemsComputedFields(t *testing.T) {
|
func TestSortCoinSellerListItemsComputedFields(t *testing.T) {
|
||||||
items := []*CoinSellerListItem{
|
items := []*CoinSellerListItem{
|
||||||
|
|||||||
@ -496,6 +496,7 @@ func rocketStateForNow(input *state.RocketState, cfg RoomRocketConfig, now time.
|
|||||||
rocket.CurrentFuel = rocket.FuelThreshold
|
rocket.CurrentFuel = rocket.FuelThreshold
|
||||||
}
|
}
|
||||||
rocket.PendingLaunches = validRocketPendingLaunches(rocket.PendingLaunches, rocket.ResetAtMS)
|
rocket.PendingLaunches = validRocketPendingLaunches(rocket.PendingLaunches, rocket.ResetAtMS)
|
||||||
|
syncRocketLaunchSnapshotFromPending(rocket)
|
||||||
if rocket.Status == state.RocketStatusCompleted {
|
if rocket.Status == state.RocketStatusCompleted {
|
||||||
rocket.CurrentLevel = roomRocketLevelCount
|
rocket.CurrentLevel = roomRocketLevelCount
|
||||||
rocket.FuelThreshold = roomRocketLevelByNumber(cfg, roomRocketLevelCount).FuelThreshold
|
rocket.FuelThreshold = roomRocketLevelByNumber(cfg, roomRocketLevelCount).FuelThreshold
|
||||||
@ -510,6 +511,31 @@ func rocketStateForNow(input *state.RocketState, cfg RoomRocketConfig, now time.
|
|||||||
return rocket
|
return rocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func syncRocketLaunchSnapshotFromPending(rocket *state.RocketState) {
|
||||||
|
if rocket == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rocket.PendingLaunches) == 0 {
|
||||||
|
// 当前充能档没有待发射火箭时,不能继续暴露上一枚火箭的发射时间,否则客户端会把过期 launch_at_ms 渲染成 00:00:00。
|
||||||
|
rocket.IgnitedAtMS = 0
|
||||||
|
rocket.LaunchAtMS = 0
|
||||||
|
rocket.Top1UserID = 0
|
||||||
|
rocket.IgniterUserID = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := rocket.PendingLaunches[0]
|
||||||
|
for _, launch := range rocket.PendingLaunches[1:] {
|
||||||
|
if launch.LaunchAtMS > 0 && (next.LaunchAtMS <= 0 || launch.LaunchAtMS < next.LaunchAtMS) {
|
||||||
|
next = launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// RoomRocketState 上的倒计时字段兼容旧客户端;真实多枚队列仍以 PendingLaunches 为准。
|
||||||
|
rocket.IgnitedAtMS = next.IgnitedAtMS
|
||||||
|
rocket.LaunchAtMS = next.LaunchAtMS
|
||||||
|
rocket.Top1UserID = next.Top1UserID
|
||||||
|
rocket.IgniterUserID = next.IgniterUserID
|
||||||
|
}
|
||||||
|
|
||||||
func validRocketPendingLaunches(input []state.RocketPendingLaunch, resetAtMS int64) []state.RocketPendingLaunch {
|
func validRocketPendingLaunches(input []state.RocketPendingLaunch, resetAtMS int64) []state.RocketPendingLaunch {
|
||||||
out := make([]state.RocketPendingLaunch, 0, len(input))
|
out := make([]state.RocketPendingLaunch, 0, len(input))
|
||||||
seen := make(map[string]bool, len(input))
|
seen := make(map[string]bool, len(input))
|
||||||
@ -821,6 +847,7 @@ func (s *Service) launchRoomRocket(ctx context.Context, cmd command.LaunchRoomRo
|
|||||||
|
|
||||||
pendingAfter := removeRocketPendingLaunch(current.Rocket.PendingLaunches, pendingIndex)
|
pendingAfter := removeRocketPendingLaunch(current.Rocket.PendingLaunches, pendingIndex)
|
||||||
current.Rocket.PendingLaunches = pendingAfter
|
current.Rocket.PendingLaunches = pendingAfter
|
||||||
|
syncRocketLaunchSnapshotFromPending(current.Rocket)
|
||||||
current.Rocket.LaunchedAtMS = nowMS
|
current.Rocket.LaunchedAtMS = nowMS
|
||||||
current.Rocket.LastRewards = rewards
|
current.Rocket.LastRewards = rewards
|
||||||
|
|
||||||
|
|||||||
@ -425,6 +425,9 @@ func TestRoomRocketLaunchGrantsLockedTop1AndIgniterAfterLeave(t *testing.T) {
|
|||||||
if stateView.GetCurrentLevel() != 2 || stateView.GetStatus() != state.RocketStatusCharging || len(stateView.GetPendingLaunches()) != 0 {
|
if stateView.GetCurrentLevel() != 2 || stateView.GetStatus() != state.RocketStatusCharging || len(stateView.GetPendingLaunches()) != 0 {
|
||||||
t.Fatalf("launch rocket must keep current next-level progress and clear pending launch: %+v", stateView)
|
t.Fatalf("launch rocket must keep current next-level progress and clear pending launch: %+v", stateView)
|
||||||
}
|
}
|
||||||
|
if stateView.GetLaunchAtMs() != 0 || stateView.GetIgnitedAtMs() != 0 || stateView.GetTop1UserId() != 0 || stateView.GetIgniterUserId() != 0 {
|
||||||
|
t.Fatalf("launched rocket must not leak expired launch countdown into next charging level: %+v", stateView)
|
||||||
|
}
|
||||||
if len(stateView.GetLastRewards()) != 3 {
|
if len(stateView.GetLastRewards()) != 3 {
|
||||||
t.Fatalf("launch rocket must retain in-room, top1 and igniter grants for client display: %+v", stateView.GetLastRewards())
|
t.Fatalf("launch rocket must retain in-room, top1 and igniter grants for client display: %+v", stateView.GetLastRewards())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -181,13 +181,13 @@ type RocketState struct {
|
|||||||
FuelThreshold int64
|
FuelThreshold int64
|
||||||
// Status 区分 charging/completed;已点火火箭在 PendingLaunches 中独立排队,不阻塞当前进度。
|
// Status 区分 charging/completed;已点火火箭在 PendingLaunches 中独立排队,不阻塞当前进度。
|
||||||
Status string
|
Status string
|
||||||
// IgnitedAtMS/LaunchAtMS 保留最近一次点火快照,客户端可用 PendingLaunches 获得完整队列。
|
// IgnitedAtMS/LaunchAtMS 投影下一枚待发射火箭,完整多枚队列仍以 PendingLaunches 为准。
|
||||||
IgnitedAtMS int64
|
IgnitedAtMS int64
|
||||||
LaunchAtMS int64
|
LaunchAtMS int64
|
||||||
LaunchedAtMS int64
|
LaunchedAtMS int64
|
||||||
// ResetAtMS 是下一次 UTC 自然日重置毫秒时间。
|
// ResetAtMS 是下一次 UTC 自然日重置毫秒时间。
|
||||||
ResetAtMS int64
|
ResetAtMS int64
|
||||||
// Top1UserID 和 IgniterUserID 在燃料满瞬间锁定,用户离房或下线仍按这里发奖。
|
// Top1UserID 和 IgniterUserID 投影下一枚待发射火箭的锁定用户;发奖以 PendingLaunches 内快照为准。
|
||||||
Top1UserID int64
|
Top1UserID int64
|
||||||
IgniterUserID int64
|
IgniterUserID int64
|
||||||
// RocketID 是单次火箭从积攒到发射的幂等标识,奖励抽取和 wallet command_id 都依赖它。
|
// RocketID 是单次火箭从积攒到发射的幂等标识,奖励抽取和 wallet command_id 都依赖它。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user