活动moban

This commit is contained in:
ZuoZuo 2026-07-15 09:43:33 +08:00
parent d164dd5fd7
commit 892e237be5
138 changed files with 43634 additions and 33345 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2377,6 +2377,533 @@ message ApplyAgencyOpeningResponse {
bool created = 2; bool created = 2;
} }
// ActivityTemplateLocale locale 使 BCP-47
// en locale -> en 退
message ActivityTemplateLocale {
string locale = 1;
string title = 2;
string rules = 3;
}
// ActivityTemplateGift
message ActivityTemplateGift {
string gift_id = 1;
int32 sort_order = 2;
string name = 3;
string icon_url = 4;
int64 coin_price = 5;
string price_version = 6;
}
// ActivityTemplateRewardItem wallet pinned snapshot
message ActivityTemplateRewardItem {
string item_type = 1;
int64 resource_id = 2;
string resource_type = 3;
string name = 4;
string icon_url = 5;
int64 quantity = 6;
int64 duration_ms = 7;
string wallet_asset_type = 8;
int64 wallet_asset_amount = 9;
int32 sort_order = 10;
}
// ActivityTemplateTask 使 wallet-service
//
message ActivityTemplateTask {
string task_key = 1;
string task_type = 2;
int64 target_value = 3;
int64 reward_resource_group_id = 4;
int32 sort_order = 5;
string reward_snapshot_id = 6;
string reward_snapshot_hash = 7;
string reward_name = 8;
repeated ActivityTemplateRewardItem reward_items = 9;
}
// ActivityTemplateRankReward
message ActivityTemplateRankReward {
string board_type = 1;
int32 rank_from = 2;
int32 rank_to = 3;
int64 resource_group_id = 4;
int32 sort_order = 5;
string reward_snapshot_id = 6;
string reward_snapshot_hash = 7;
string reward_name = 8;
repeated ActivityTemplateRewardItem reward_items = 9;
}
// ActivityTemplateAsset locale="*"
// media_type image/webp/svga/pag
message ActivityTemplateAsset {
string asset_key = 1;
string locale = 2;
string url = 3;
string media_type = 4;
int32 sort_order = 5;
}
// ActivityTemplate 沿
// activity-service
message ActivityTemplate {
string app_code = 1;
string template_id = 2;
string template_code = 3;
string name = 4;
string activity_type = 5;
string status = 6;
string lifecycle_status = 7;
int64 start_ms = 8;
int64 end_ms = 9;
bool all_regions = 10;
repeated int64 region_ids = 11;
repeated ActivityTemplateLocale locales = 12;
repeated ActivityTemplateGift gifts = 13;
repeated ActivityTemplateTask tasks = 14;
int32 daily_leaderboard_size = 15;
int32 total_leaderboard_size = 16;
repeated ActivityTemplateRankReward rank_rewards = 17;
repeated ActivityTemplateAsset assets = 18;
string display_config_json = 19;
int64 revision = 20;
int64 published_version = 21;
int64 created_by_admin_id = 22;
int64 updated_by_admin_id = 23;
int64 published_by_admin_id = 24;
int64 created_at_ms = 25;
int64 updated_at_ms = 26;
int64 published_at_ms = 27;
int64 archived_at_ms = 28;
}
// ActivityTemplateSummary JSON
//
message ActivityTemplateSummary {
string app_code = 1;
string template_id = 2;
string template_code = 3;
string name = 4;
string activity_type = 5;
string status = 6;
string lifecycle_status = 7;
int64 start_ms = 8;
int64 end_ms = 9;
bool all_regions = 10;
repeated int64 region_ids = 11;
int64 revision = 12;
int64 published_version = 13;
int64 created_by_admin_id = 14;
int64 updated_by_admin_id = 15;
int64 created_at_ms = 16;
int64 updated_at_ms = 17;
}
message ListActivityTemplatesRequest {
RequestMeta meta = 1;
string keyword = 2;
string status = 3;
optional int64 region_id = 4;
int64 start_ms = 5;
int64 end_ms = 6;
int32 page = 7;
int32 page_size = 8;
optional bool all_regions = 9;
}
message ListActivityTemplatesResponse {
repeated ActivityTemplateSummary templates = 1;
int64 total = 2;
int64 server_time_ms = 3;
}
message CreateActivityTemplateRequest {
RequestMeta meta = 1;
ActivityTemplate template = 2;
int64 operator_admin_id = 3;
}
message CreateActivityTemplateResponse {
ActivityTemplate template = 1;
}
message GetActivityTemplateRequest {
RequestMeta meta = 1;
string template_id = 2;
}
message GetActivityTemplateResponse {
ActivityTemplate template = 1;
}
message UpdateActivityTemplateRequest {
RequestMeta meta = 1;
ActivityTemplate template = 2;
int64 expected_revision = 3;
int64 operator_admin_id = 4;
}
message UpdateActivityTemplateResponse {
ActivityTemplate template = 1;
}
message SetActivityTemplateStatusRequest {
RequestMeta meta = 1;
string template_id = 2;
string status = 3;
int64 expected_revision = 4;
int64 operator_admin_id = 5;
}
message SetActivityTemplateStatusResponse {
ActivityTemplate template = 1;
}
message DeleteActivityTemplateRequest {
RequestMeta meta = 1;
string template_id = 2;
int64 expected_revision = 3;
int64 operator_admin_id = 4;
}
message DeleteActivityTemplateResponse {
ActivityTemplate template = 1;
bool archived = 2;
}
// ActivityTemplateVersion
message ActivityTemplateVersion {
string template_id = 1;
int64 version_no = 2;
ActivityTemplate snapshot = 3;
int64 published_by_admin_id = 4;
int64 published_at_ms = 5;
// activity_template_runtime_versions runtime_to_ms
int64 runtime_from_ms = 6;
int64 runtime_to_ms = 7;
}
message ListActivityTemplateVersionsRequest {
RequestMeta meta = 1;
string template_id = 2;
int32 page = 3;
int32 page_size = 4;
}
message ListActivityTemplateVersionsResponse {
repeated ActivityTemplateVersion versions = 1;
int64 total = 2;
}
// CloneActivityTemplateRequest 稿
// source_version=0 template_code App
message CloneActivityTemplateRequest {
RequestMeta meta = 1;
string source_template_id = 2;
int64 source_version = 3;
string template_code = 4;
string name = 5;
int64 operator_admin_id = 6;
}
message CloneActivityTemplateResponse {
ActivityTemplate template = 1;
}
message ActivityTemplateValidationIssue {
string field = 1;
string code = 2;
string message = 3;
}
// ValidateActivityTemplateRequest owner service
message ValidateActivityTemplateRequest {
RequestMeta meta = 1;
ActivityTemplate template = 2;
bool for_publish = 3;
}
message ValidateActivityTemplateResponse {
bool valid = 1;
repeated ActivityTemplateValidationIssue issues = 2;
}
// ActivityTemplateRuntime
// runtime_from_ms/runtime_to_ms template [start_ms,end_ms)
message ActivityTemplateRuntime {
ActivityTemplate template = 1;
int64 version_no = 2;
int64 runtime_from_ms = 3;
int64 runtime_to_ms = 4;
int64 server_time_ms = 5;
}
// ActivityTemplateRuntimeTask UTC
message ActivityTemplateRuntimeTask {
string template_id = 1;
string template_code = 2;
int64 version_no = 3;
string task_day = 4;
string task_key = 5;
string task_type = 6;
int64 target_value = 7;
int64 progress_value = 8;
int64 reward_resource_group_id = 9;
string status = 10;
bool claimable = 11;
int64 completed_at_ms = 12;
int64 claimed_at_ms = 13;
int32 sort_order = 14;
string reward_snapshot_id = 15;
string reward_snapshot_hash = 16;
string reward_name = 17;
repeated ActivityTemplateRewardItem reward_items = 18;
}
// ActivityTemplateTaskClaim activity-service wallet-service
message ActivityTemplateTaskClaim {
string claim_id = 1;
string command_id = 2;
string template_id = 3;
string template_code = 4;
int64 version_no = 5;
string task_day = 6;
string task_key = 7;
int64 user_id = 8;
int64 reward_resource_group_id = 9;
string wallet_command_id = 10;
string wallet_grant_id = 11;
string status = 12;
string failure_reason = 13;
int32 attempt_count = 14;
int64 created_at_ms = 15;
int64 updated_at_ms = 16;
int64 granted_at_ms = 17;
string reward_snapshot_id = 18;
string reward_name = 19;
repeated ActivityTemplateRewardItem reward_items = 20;
string reward_snapshot_hash = 21;
// earning_region_id/attribution_at_ms are frozen from the progress event that completed the task.
int64 earning_region_id = 22;
int64 attribution_at_ms = 23;
}
// ActivityTemplateLeaderboardEntry
message ActivityTemplateLeaderboardEntry {
int32 rank_no = 1;
int64 user_id = 2;
int64 score = 3;
int64 gift_count = 4;
int64 coin_amount = 5;
int64 first_scored_at_ms = 6;
bool snapshot = 7;
}
message GetCurrentActivityTemplateRequest {
RequestMeta meta = 1;
int64 user_id = 2;
int64 region_id = 3;
string locale = 4;
}
message GetCurrentActivityTemplateResponse {
ActivityTemplateRuntime runtime = 1;
bool found = 2;
}
// VisitActivityTemplate daily_visit command_id
// ++UTC task_day
message VisitActivityTemplateRequest {
RequestMeta meta = 1;
int64 user_id = 2;
int64 region_id = 3;
string template_code = 4;
string command_id = 5;
// version_no comes from GetCurrentActivityTemplate and prevents disable/re-publish races.
int64 version_no = 6;
}
message VisitActivityTemplateResponse {
ActivityTemplateRuntime runtime = 1;
repeated ActivityTemplateRuntimeTask tasks = 2;
bool first_visit = 3;
}
message ListActivityTemplateTasksRequest {
RequestMeta meta = 1;
int64 user_id = 2;
int64 region_id = 3;
string template_code = 4;
string task_day = 5;
// version_no task_day
int64 version_no = 6;
}
message ListActivityTemplateTasksResponse {
ActivityTemplateRuntime runtime = 1;
repeated ActivityTemplateRuntimeTask tasks = 2;
int64 next_refresh_at_ms = 3;
}
message ClaimActivityTemplateTaskRewardRequest {
RequestMeta meta = 1;
int64 user_id = 2;
int64 region_id = 3;
string template_code = 4;
string task_day = 5;
string task_key = 6;
string command_id = 7;
// version_no tasks/runtime
int64 version_no = 8;
}
message ClaimActivityTemplateTaskRewardResponse {
ActivityTemplateTaskClaim claim = 1;
bool claimed = 2;
}
message ListActivityTemplateLeaderboardRequest {
RequestMeta meta = 1;
int64 user_id = 2;
int64 region_id = 3;
string template_code = 4;
string board_type = 5;
string period_key = 6;
int32 page = 7;
int32 page_size = 8;
// version_no > 0 /
int64 version_no = 9;
}
message ListActivityTemplateLeaderboardResponse {
ActivityTemplateRuntime runtime = 1;
string board_type = 2;
string period_key = 3;
int64 period_start_ms = 4;
int64 period_end_ms = 5;
string period_status = 6;
repeated ActivityTemplateLeaderboardEntry entries = 7;
ActivityTemplateLeaderboardEntry my_entry = 8;
int64 total = 9;
// settle_after_ms
int64 settle_after_ms = 10;
}
// ActivityTemplateRankPeriod UTC /
message ActivityTemplateRankPeriod {
string template_id = 1;
string template_code = 2;
int64 version_no = 3;
string board_type = 4;
string period_key = 5;
int64 period_start_ms = 6;
int64 period_end_ms = 7;
// pending -> settling-> rewarding job -> settled cancelled
string status = 8;
int64 settled_at_ms = 9;
int64 settle_after_ms = 10;
}
message ActivityTemplateRewardJob {
string reward_job_id = 1;
string template_id = 2;
string template_code = 3;
int64 version_no = 4;
string board_type = 5;
string period_key = 6;
int32 rank_no = 7;
int64 user_id = 8;
int64 score = 9;
int64 resource_group_id = 10;
string wallet_command_id = 11;
string wallet_grant_id = 12;
string status = 13;
string failure_reason = 14;
int32 attempt_count = 15;
int64 created_at_ms = 16;
int64 updated_at_ms = 17;
int64 granted_at_ms = 18;
string reward_snapshot_id = 19;
string reward_snapshot_hash = 20;
string reward_name = 21;
repeated ActivityTemplateRewardItem reward_items = 22;
int32 max_attempts = 23;
int64 next_retry_at_ms = 24;
}
message AdminListActivityTemplateLeaderboardRequest {
RequestMeta meta = 1;
string template_id = 2;
int64 version_no = 3;
string board_type = 4;
string period_key = 5;
int32 page = 6;
int32 page_size = 7;
}
message AdminListActivityTemplateLeaderboardResponse {
ActivityTemplateRankPeriod period = 1;
repeated ActivityTemplateLeaderboardEntry entries = 2;
int64 total = 3;
}
message ListActivityTemplateRewardJobsRequest {
RequestMeta meta = 1;
string template_id = 2;
int64 version_no = 3;
string board_type = 4;
string period_key = 5;
string status = 6;
int32 page = 7;
int32 page_size = 8;
}
message ListActivityTemplateRewardJobsResponse {
repeated ActivityTemplateRewardJob jobs = 1;
int64 total = 2;
}
message RetryActivityTemplateRewardJobRequest {
RequestMeta meta = 1;
string template_id = 2;
string reward_job_id = 3;
int64 operator_admin_id = 4;
}
message RetryActivityTemplateRewardJobResponse {
ActivityTemplateRewardJob job = 1;
}
message ListActivityTemplateTaskClaimsRequest {
RequestMeta meta = 1;
string template_id = 2;
int64 version_no = 3;
string task_day = 4;
string task_key = 5;
int64 user_id = 6;
string status = 7;
int32 page = 8;
int32 page_size = 9;
}
message ListActivityTemplateTaskClaimsResponse {
repeated ActivityTemplateTaskClaim claims = 1;
int64 total = 2;
}
message RetryActivityTemplateTaskClaimRequest {
RequestMeta meta = 1;
string template_id = 2;
string claim_id = 3;
int64 operator_admin_id = 4;
}
message RetryActivityTemplateTaskClaimResponse {
ActivityTemplateTaskClaim claim = 1;
}
// ActivityService RPC // ActivityService RPC
service ActivityService { service ActivityService {
rpc PingActivity(PingActivityRequest) returns (PingActivityResponse); rpc PingActivity(PingActivityRequest) returns (PingActivityResponse);
@ -2412,6 +2939,16 @@ service ActivityCronService {
rpc ProcessWeeklyStarSettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessWeeklyStarSettlementBatch(CronBatchRequest) returns (CronBatchResponse);
rpc ProcessCPWeeklyRankSettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessCPWeeklyRankSettlementBatch(CronBatchRequest) returns (CronBatchResponse);
rpc ProcessAgencyOpeningSettlementBatch(CronBatchRequest) returns (CronBatchResponse); rpc ProcessAgencyOpeningSettlementBatch(CronBatchRequest) returns (CronBatchResponse);
rpc ProcessActivityTemplateSettlementBatch(CronBatchRequest) returns (CronBatchResponse);
}
// ActivityTemplateRuntimeService App/H5 owner
service ActivityTemplateRuntimeService {
rpc GetCurrentActivityTemplate(GetCurrentActivityTemplateRequest) returns (GetCurrentActivityTemplateResponse);
rpc VisitActivityTemplate(VisitActivityTemplateRequest) returns (VisitActivityTemplateResponse);
rpc ListActivityTemplateTasks(ListActivityTemplateTasksRequest) returns (ListActivityTemplateTasksResponse);
rpc ClaimActivityTemplateTaskReward(ClaimActivityTemplateTaskRewardRequest) returns (ClaimActivityTemplateTaskRewardResponse);
rpc ListActivityTemplateLeaderboard(ListActivityTemplateLeaderboardRequest) returns (ListActivityTemplateLeaderboardResponse);
} }
// TaskService App // TaskService App
@ -2582,6 +3119,25 @@ service AdminAgencyOpeningService {
rpc ReviewAgencyOpeningApplication(ReviewAgencyOpeningApplicationRequest) returns (ReviewAgencyOpeningApplicationResponse); rpc ReviewAgencyOpeningApplication(ReviewAgencyOpeningApplicationRequest) returns (ReviewAgencyOpeningApplicationResponse);
} }
// AdminActivityTemplateService 访 activity-service
// admin-server activity-service
service AdminActivityTemplateService {
rpc ListActivityTemplates(ListActivityTemplatesRequest) returns (ListActivityTemplatesResponse);
rpc CreateActivityTemplate(CreateActivityTemplateRequest) returns (CreateActivityTemplateResponse);
rpc GetActivityTemplate(GetActivityTemplateRequest) returns (GetActivityTemplateResponse);
rpc UpdateActivityTemplate(UpdateActivityTemplateRequest) returns (UpdateActivityTemplateResponse);
rpc SetActivityTemplateStatus(SetActivityTemplateStatusRequest) returns (SetActivityTemplateStatusResponse);
rpc DeleteActivityTemplate(DeleteActivityTemplateRequest) returns (DeleteActivityTemplateResponse);
rpc ListActivityTemplateVersions(ListActivityTemplateVersionsRequest) returns (ListActivityTemplateVersionsResponse);
rpc CloneActivityTemplate(CloneActivityTemplateRequest) returns (CloneActivityTemplateResponse);
rpc ValidateActivityTemplate(ValidateActivityTemplateRequest) returns (ValidateActivityTemplateResponse);
rpc ListActivityTemplateLeaderboard(AdminListActivityTemplateLeaderboardRequest) returns (AdminListActivityTemplateLeaderboardResponse);
rpc ListActivityTemplateRewardJobs(ListActivityTemplateRewardJobsRequest) returns (ListActivityTemplateRewardJobsResponse);
rpc RetryActivityTemplateRewardJob(RetryActivityTemplateRewardJobRequest) returns (RetryActivityTemplateRewardJobResponse);
rpc ListActivityTemplateTaskClaims(ListActivityTemplateTaskClaimsRequest) returns (ListActivityTemplateTaskClaimsResponse);
rpc RetryActivityTemplateTaskClaim(RetryActivityTemplateTaskClaimRequest) returns (RetryActivityTemplateTaskClaimResponse);
}
// SevenDayCheckInService App // SevenDayCheckInService App
service SevenDayCheckInService { service SevenDayCheckInService {
rpc GetSevenDayCheckInStatus(GetSevenDayCheckInStatusRequest) returns (GetSevenDayCheckInStatusResponse); rpc GetSevenDayCheckInStatus(GetSevenDayCheckInStatusRequest) returns (GetSevenDayCheckInStatusResponse);

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v5.29.2
// source: proto/events/luckygift/v1/events.proto // source: proto/events/luckygift/v1/events.proto
package luckygifteventsv1 package luckygifteventsv1
@ -11,6 +11,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@ -23,10 +24,7 @@ const (
// EventEnvelope 是 lucky-gift-service 发布 owner outbox 事实的稳定信封。 // EventEnvelope 是 lucky-gift-service 发布 owner outbox 事实的稳定信封。
// occurred_at_ms 固定为抽奖事实创建时的 Unix epoch millisecondsMQ 实际发送时间不改变业务发生时间。 // occurred_at_ms 固定为抽奖事实创建时的 Unix epoch millisecondsMQ 实际发送时间不改变业务发生时间。
type EventEnvelope struct { type EventEnvelope struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
EventType string `protobuf:"bytes,2,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` EventType string `protobuf:"bytes,2,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"`
AppCode string `protobuf:"bytes,3,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` AppCode string `protobuf:"bytes,3,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
@ -34,6 +32,8 @@ type EventEnvelope struct {
DrawId string `protobuf:"bytes,4,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` DrawId string `protobuf:"bytes,4,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"`
OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"`
Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *EventEnvelope) Reset() { func (x *EventEnvelope) Reset() {
@ -112,10 +112,7 @@ func (x *EventEnvelope) GetBody() []byte {
// lucky-gift-service 必须先完成 wallet-service 入账和本地 granted 收敛,再发布本事件; // lucky-gift-service 必须先完成 wallet-service 入账和本地 granted 收敛,再发布本事件;
// room-service 与 activity-service 只从该事实派生房内 IM 和区域飘屏,不能反向改变抽奖或账务状态。 // room-service 与 activity-service 只从该事实派生房内 IM 和区域飘屏,不能反向改变抽奖或账务状态。
type LuckyGiftDrawn struct { type LuckyGiftDrawn struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"`
CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"`
RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
@ -147,6 +144,8 @@ type LuckyGiftDrawn struct {
// 两个时间均为 UTC Unix epoch milliseconds抽奖发生时间与钱包返奖确认时间不能混用。 // 两个时间均为 UTC Unix epoch milliseconds抽奖发生时间与钱包返奖确认时间不能混用。
DrawCreatedAtMs int64 `protobuf:"varint,26,opt,name=draw_created_at_ms,json=drawCreatedAtMs,proto3" json:"draw_created_at_ms,omitempty"` DrawCreatedAtMs int64 `protobuf:"varint,26,opt,name=draw_created_at_ms,json=drawCreatedAtMs,proto3" json:"draw_created_at_ms,omitempty"`
RewardGrantedAtMs int64 `protobuf:"varint,27,opt,name=reward_granted_at_ms,json=rewardGrantedAtMs,proto3" json:"reward_granted_at_ms,omitempty"` RewardGrantedAtMs int64 `protobuf:"varint,27,opt,name=reward_granted_at_ms,json=rewardGrantedAtMs,proto3" json:"reward_granted_at_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *LuckyGiftDrawn) Reset() { func (x *LuckyGiftDrawn) Reset() {
@ -370,105 +369,60 @@ func (x *LuckyGiftDrawn) GetRewardGrantedAtMs() int64 {
var File_proto_events_luckygift_v1_events_proto protoreflect.FileDescriptor var File_proto_events_luckygift_v1_events_proto protoreflect.FileDescriptor
var file_proto_events_luckygift_v1_events_proto_rawDesc = []byte{ const file_proto_events_luckygift_v1_events_proto_rawDesc = "" +
0x0a, 0x26, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6c, "\n" +
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, "&proto/events/luckygift/v1/events.proto\x12\x19hyapp.events.luckygift.v1\"\xb7\x01\n" +
0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, "\rEventEnvelope\x12\x19\n" +
0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, "\bevent_id\x18\x01 \x01(\tR\aeventId\x12\x1d\n" +
0x2e, 0x76, 0x31, 0x22, 0xb7, 0x01, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x76, "\n" +
0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, "event_type\x18\x02 \x01(\tR\teventType\x12\x19\n" +
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, "\bapp_code\x18\x03 \x01(\tR\aappCode\x12\x17\n" +
0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, "\adraw_id\x18\x04 \x01(\tR\x06drawId\x12$\n" +
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, "\x0eoccurred_at_ms\x18\x05 \x01(\x03R\foccurredAtMs\x12\x12\n" +
0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, "\x04body\x18\x06 \x01(\fR\x04body\"\xab\b\n" +
0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, "\x0eLuckyGiftDrawn\x12\x17\n" +
0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, "\adraw_id\x18\x01 \x01(\tR\x06drawId\x12\x1d\n" +
0x77, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x5f, "\n" +
0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x63, 0x63, "command_id\x18\x02 \x01(\tR\tcommandId\x12\x17\n" +
0x75, 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, "\aroom_id\x18\x03 \x01(\tR\x06roomId\x12\x17\n" +
0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xab, 0x08, "\apool_id\x18\x04 \x01(\tR\x06poolId\x12\x17\n" +
0x0a, 0x0e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, "\agift_id\x18\x05 \x01(\tR\x06giftId\x12\x1d\n" +
0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, "\n" +
0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, "gift_count\x18\x06 \x01(\x05R\tgiftCount\x12$\n" +
0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, "\x0esender_user_id\x18\a \x01(\x03R\fsenderUserId\x12$\n" +
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, "\x0etarget_user_id\x18\b \x01(\x03R\ftargetUserId\x12\x1f\n" +
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, "\vsender_name\x18\t \x01(\tR\n" +
0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, "senderName\x12#\n" +
0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, "\rsender_avatar\x18\n" +
0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, " \x01(\tR\fsenderAvatar\x123\n" +
0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, "\x16sender_display_user_id\x18\v \x01(\tR\x13senderDisplayUserId\x12@\n" +
0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, "\x1dsender_pretty_display_user_id\x18\f \x01(\tR\x19senderPrettyDisplayUserId\x12*\n" +
0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, "\x11visible_region_id\x18\r \x01(\x03R\x0fvisibleRegionId\x12\x1d\n" +
0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, "\n" +
0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, "country_id\x18\x0e \x01(\x03R\tcountryId\x12\x1d\n" +
0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, "\n" +
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, "coin_spent\x18\x0f \x01(\x03R\tcoinSpent\x12!\n" +
0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, "\frule_version\x18\x10 \x01(\x03R\vruleVersion\x12'\n" +
0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, "\x0fexperience_pool\x18\x11 \x01(\tR\x0eexperiencePool\x12(\n" +
0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, "\x10selected_tier_id\x18\x12 \x01(\tR\x0eselectedTierId\x12%\n" +
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x76, "\x0emultiplier_ppm\x18\x13 \x01(\x03R\rmultiplierPpm\x12*\n" +
0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x64, "\x11base_reward_coins\x18\x14 \x01(\x03R\x0fbaseRewardCoins\x124\n" +
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, "\x16effective_reward_coins\x18\x15 \x01(\x03R\x14effectiveRewardCoins\x12%\n" +
0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73, 0x70, "\x0estage_feedback\x18\x16 \x01(\bR\rstageFeedback\x12'\n" +
0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65, 0x6e, "\x0fhigh_multiplier\x18\x17 \x01(\bR\x0ehighMultiplier\x12#\n" +
0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, "\rreward_status\x18\x18 \x01(\tR\frewardStatus\x122\n" +
0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, "\x15wallet_transaction_id\x18\x19 \x01(\tR\x13walletTransactionId\x12+\n" +
0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, "\x12draw_created_at_ms\x18\x1a \x01(\x03R\x0fdrawCreatedAtMs\x12/\n" +
0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, "\x14reward_granted_at_ms\x18\x1b \x01(\x03R\x11rewardGrantedAtMsB=Z;hyapp.local/api/proto/events/luckygift/v1;luckygifteventsv1b\x06proto3"
0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52,
0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73,
0x70, 0x65, 0x6e, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e,
0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x75, 0x6c,
0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x65,
0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x50, 0x6f, 0x6f,
0x6c, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d,
0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x13, 0x20,
0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50,
0x70, 0x6d, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72,
0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62,
0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34,
0x0a, 0x16, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61,
0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14,
0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43,
0x6f, 0x69, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x65,
0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74,
0x61, 0x67, 0x65, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x27, 0x0a, 0x0f, 0x68,
0x69, 0x67, 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x17,
0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x69, 0x67, 0x68, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70,
0x6c, 0x69, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77,
0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a,
0x12, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
0x5f, 0x6d, 0x73, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x72, 0x61, 0x77, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65,
0x77, 0x61, 0x72, 0x64, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f,
0x6d, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64,
0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x42, 0x3d, 0x5a, 0x3b, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6c, 0x75, 0x63, 0x6b,
0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69,
0x66, 0x74, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var ( var (
file_proto_events_luckygift_v1_events_proto_rawDescOnce sync.Once file_proto_events_luckygift_v1_events_proto_rawDescOnce sync.Once
file_proto_events_luckygift_v1_events_proto_rawDescData = file_proto_events_luckygift_v1_events_proto_rawDesc file_proto_events_luckygift_v1_events_proto_rawDescData []byte
) )
func file_proto_events_luckygift_v1_events_proto_rawDescGZIP() []byte { func file_proto_events_luckygift_v1_events_proto_rawDescGZIP() []byte {
file_proto_events_luckygift_v1_events_proto_rawDescOnce.Do(func() { file_proto_events_luckygift_v1_events_proto_rawDescOnce.Do(func() {
file_proto_events_luckygift_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_events_luckygift_v1_events_proto_rawDescData) file_proto_events_luckygift_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_events_luckygift_v1_events_proto_rawDesc), len(file_proto_events_luckygift_v1_events_proto_rawDesc)))
}) })
return file_proto_events_luckygift_v1_events_proto_rawDescData return file_proto_events_luckygift_v1_events_proto_rawDescData
} }
@ -495,7 +449,7 @@ func file_proto_events_luckygift_v1_events_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_events_luckygift_v1_events_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_events_luckygift_v1_events_proto_rawDesc), len(file_proto_events_luckygift_v1_events_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 2, NumMessages: 2,
NumExtensions: 0, NumExtensions: 0,
@ -506,7 +460,6 @@ func file_proto_events_luckygift_v1_events_proto_init() {
MessageInfos: file_proto_events_luckygift_v1_events_proto_msgTypes, MessageInfos: file_proto_events_luckygift_v1_events_proto_msgTypes,
}.Build() }.Build()
File_proto_events_luckygift_v1_events_proto = out.File File_proto_events_luckygift_v1_events_proto = out.File
file_proto_events_luckygift_v1_events_proto_rawDesc = nil
file_proto_events_luckygift_v1_events_proto_goTypes = nil file_proto_events_luckygift_v1_events_proto_goTypes = nil
file_proto_events_luckygift_v1_events_proto_depIdxs = nil file_proto_events_luckygift_v1_events_proto_depIdxs = nil
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/game/v1/game.proto // source: proto/game/v1/game.proto
package gamev1 package gamev1
@ -271,55 +271,55 @@ type GameAppServiceServer interface {
type UnimplementedGameAppServiceServer struct{} type UnimplementedGameAppServiceServer struct{}
func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) { func (UnimplementedGameAppServiceServer) ListGames(context.Context, *ListGamesRequest) (*ListGamesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGames not implemented") return nil, status.Error(codes.Unimplemented, "method ListGames not implemented")
} }
func (UnimplementedGameAppServiceServer) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) { func (UnimplementedGameAppServiceServer) ListRecentGames(context.Context, *ListRecentGamesRequest) (*ListGamesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRecentGames not implemented") return nil, status.Error(codes.Unimplemented, "method ListRecentGames not implemented")
} }
func (UnimplementedGameAppServiceServer) ListExploreWinners(context.Context, *ListExploreWinnersRequest) (*ListExploreWinnersResponse, error) { func (UnimplementedGameAppServiceServer) ListExploreWinners(context.Context, *ListExploreWinnersRequest) (*ListExploreWinnersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListExploreWinners not implemented") return nil, status.Error(codes.Unimplemented, "method ListExploreWinners not implemented")
} }
func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) { func (UnimplementedGameAppServiceServer) GetBridgeScript(context.Context, *GetBridgeScriptRequest) (*GetBridgeScriptResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBridgeScript not implemented") return nil, status.Error(codes.Unimplemented, "method GetBridgeScript not implemented")
} }
func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) { func (UnimplementedGameAppServiceServer) LaunchGame(context.Context, *LaunchGameRequest) (*LaunchGameResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LaunchGame not implemented") return nil, status.Error(codes.Unimplemented, "method LaunchGame not implemented")
} }
func (UnimplementedGameAppServiceServer) GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) { func (UnimplementedGameAppServiceServer) GetDiceConfig(context.Context, *GetDiceConfigRequest) (*DiceConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDiceConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetDiceConfig not implemented")
} }
func (UnimplementedGameAppServiceServer) MatchDice(context.Context, *MatchDiceRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) MatchDice(context.Context, *MatchDiceRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MatchDice not implemented") return nil, status.Error(codes.Unimplemented, "method MatchDice not implemented")
} }
func (UnimplementedGameAppServiceServer) CreateDiceMatch(context.Context, *CreateDiceMatchRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) CreateDiceMatch(context.Context, *CreateDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateDiceMatch not implemented") return nil, status.Error(codes.Unimplemented, "method CreateDiceMatch not implemented")
} }
func (UnimplementedGameAppServiceServer) JoinDiceMatch(context.Context, *JoinDiceMatchRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) JoinDiceMatch(context.Context, *JoinDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinDiceMatch not implemented") return nil, status.Error(codes.Unimplemented, "method JoinDiceMatch not implemented")
} }
func (UnimplementedGameAppServiceServer) GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) GetDiceMatch(context.Context, *GetDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDiceMatch not implemented") return nil, status.Error(codes.Unimplemented, "method GetDiceMatch not implemented")
} }
func (UnimplementedGameAppServiceServer) RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) RollDiceMatch(context.Context, *RollDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RollDiceMatch not implemented") return nil, status.Error(codes.Unimplemented, "method RollDiceMatch not implemented")
} }
func (UnimplementedGameAppServiceServer) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) { func (UnimplementedGameAppServiceServer) CancelDiceMatch(context.Context, *CancelDiceMatchRequest) (*DiceMatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CancelDiceMatch not implemented") return nil, status.Error(codes.Unimplemented, "method CancelDiceMatch not implemented")
} }
func (UnimplementedGameAppServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { func (UnimplementedGameAppServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
} }
func (UnimplementedGameAppServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) { func (UnimplementedGameAppServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRPSChallenges not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
} }
func (UnimplementedGameAppServiceServer) CreateRoomRPSChallenge(context.Context, *CreateRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAppServiceServer) CreateRoomRPSChallenge(context.Context, *CreateRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRoomRPSChallenge not implemented") return nil, status.Error(codes.Unimplemented, "method CreateRoomRPSChallenge not implemented")
} }
func (UnimplementedGameAppServiceServer) AcceptRoomRPSChallenge(context.Context, *AcceptRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAppServiceServer) AcceptRoomRPSChallenge(context.Context, *AcceptRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AcceptRoomRPSChallenge not implemented") return nil, status.Error(codes.Unimplemented, "method AcceptRoomRPSChallenge not implemented")
} }
func (UnimplementedGameAppServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAppServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSChallenge not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
} }
func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {} func (UnimplementedGameAppServiceServer) mustEmbedUnimplementedGameAppServiceServer() {}
func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {} func (UnimplementedGameAppServiceServer) testEmbeddedByValue() {}
@ -332,7 +332,7 @@ type UnsafeGameAppServiceServer interface {
} }
func RegisterGameAppServiceServer(s grpc.ServiceRegistrar, srv GameAppServiceServer) { func RegisterGameAppServiceServer(s grpc.ServiceRegistrar, srv GameAppServiceServer) {
// If the following call pancis, it indicates UnimplementedGameAppServiceServer was // If the following call panics, it indicates UnimplementedGameAppServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -773,7 +773,7 @@ type GameCallbackServiceServer interface {
type UnimplementedGameCallbackServiceServer struct{} type UnimplementedGameCallbackServiceServer struct{}
func (UnimplementedGameCallbackServiceServer) HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) { func (UnimplementedGameCallbackServiceServer) HandleCallback(context.Context, *CallbackRequest) (*CallbackResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method HandleCallback not implemented") return nil, status.Error(codes.Unimplemented, "method HandleCallback not implemented")
} }
func (UnimplementedGameCallbackServiceServer) mustEmbedUnimplementedGameCallbackServiceServer() {} func (UnimplementedGameCallbackServiceServer) mustEmbedUnimplementedGameCallbackServiceServer() {}
func (UnimplementedGameCallbackServiceServer) testEmbeddedByValue() {} func (UnimplementedGameCallbackServiceServer) testEmbeddedByValue() {}
@ -786,7 +786,7 @@ type UnsafeGameCallbackServiceServer interface {
} }
func RegisterGameCallbackServiceServer(s grpc.ServiceRegistrar, srv GameCallbackServiceServer) { func RegisterGameCallbackServiceServer(s grpc.ServiceRegistrar, srv GameCallbackServiceServer) {
// If the following call pancis, it indicates UnimplementedGameCallbackServiceServer was // If the following call panics, it indicates UnimplementedGameCallbackServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -879,7 +879,7 @@ type GameCronServiceServer interface {
type UnimplementedGameCronServiceServer struct{} type UnimplementedGameCronServiceServer struct{}
func (UnimplementedGameCronServiceServer) ProcessLevelEventOutboxBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedGameCronServiceServer) ProcessLevelEventOutboxBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessLevelEventOutboxBatch not implemented")
} }
func (UnimplementedGameCronServiceServer) mustEmbedUnimplementedGameCronServiceServer() {} func (UnimplementedGameCronServiceServer) mustEmbedUnimplementedGameCronServiceServer() {}
func (UnimplementedGameCronServiceServer) testEmbeddedByValue() {} func (UnimplementedGameCronServiceServer) testEmbeddedByValue() {}
@ -892,7 +892,7 @@ type UnsafeGameCronServiceServer interface {
} }
func RegisterGameCronServiceServer(s grpc.ServiceRegistrar, srv GameCronServiceServer) { func RegisterGameCronServiceServer(s grpc.ServiceRegistrar, srv GameCronServiceServer) {
// If the following call pancis, it indicates UnimplementedGameCronServiceServer was // If the following call panics, it indicates UnimplementedGameCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1319,85 +1319,85 @@ type GameAdminServiceServer interface {
type UnimplementedGameAdminServiceServer struct{} type UnimplementedGameAdminServiceServer struct{}
func (UnimplementedGameAdminServiceServer) ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) { func (UnimplementedGameAdminServiceServer) ListPlatforms(context.Context, *ListPlatformsRequest) (*ListPlatformsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPlatforms not implemented") return nil, status.Error(codes.Unimplemented, "method ListPlatforms not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) { func (UnimplementedGameAdminServiceServer) UpsertPlatform(context.Context, *UpsertPlatformRequest) (*PlatformResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertPlatform not implemented") return nil, status.Error(codes.Unimplemented, "method UpsertPlatform not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) { func (UnimplementedGameAdminServiceServer) ListCatalog(context.Context, *ListCatalogRequest) (*ListCatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCatalog not implemented") return nil, status.Error(codes.Unimplemented, "method ListCatalog not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) { func (UnimplementedGameAdminServiceServer) UpsertCatalog(context.Context, *UpsertCatalogRequest) (*CatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertCatalog not implemented") return nil, status.Error(codes.Unimplemented, "method UpsertCatalog not implemented")
} }
func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) { func (UnimplementedGameAdminServiceServer) SetGameStatus(context.Context, *SetGameStatusRequest) (*CatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGameStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetGameStatus not implemented")
} }
func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) { func (UnimplementedGameAdminServiceServer) DeleteCatalog(context.Context, *DeleteCatalogRequest) (*DeleteCatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteCatalog not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteCatalog not implemented")
} }
func (UnimplementedGameAdminServiceServer) SetGameWhitelistEnabled(context.Context, *SetGameWhitelistEnabledRequest) (*CatalogResponse, error) { func (UnimplementedGameAdminServiceServer) SetGameWhitelistEnabled(context.Context, *SetGameWhitelistEnabledRequest) (*CatalogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGameWhitelistEnabled not implemented") return nil, status.Error(codes.Unimplemented, "method SetGameWhitelistEnabled not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListGameWhitelistUsers(context.Context, *ListGameWhitelistUsersRequest) (*ListGameWhitelistUsersResponse, error) { func (UnimplementedGameAdminServiceServer) ListGameWhitelistUsers(context.Context, *ListGameWhitelistUsersRequest) (*ListGameWhitelistUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGameWhitelistUsers not implemented") return nil, status.Error(codes.Unimplemented, "method ListGameWhitelistUsers not implemented")
} }
func (UnimplementedGameAdminServiceServer) AddGameWhitelistUser(context.Context, *AddGameWhitelistUserRequest) (*GameWhitelistUserResponse, error) { func (UnimplementedGameAdminServiceServer) AddGameWhitelistUser(context.Context, *AddGameWhitelistUserRequest) (*GameWhitelistUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddGameWhitelistUser not implemented") return nil, status.Error(codes.Unimplemented, "method AddGameWhitelistUser not implemented")
} }
func (UnimplementedGameAdminServiceServer) DeleteGameWhitelistUser(context.Context, *DeleteGameWhitelistUserRequest) (*DeleteGameWhitelistUserResponse, error) { func (UnimplementedGameAdminServiceServer) DeleteGameWhitelistUser(context.Context, *DeleteGameWhitelistUserRequest) (*DeleteGameWhitelistUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteGameWhitelistUser not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteGameWhitelistUser not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) { func (UnimplementedGameAdminServiceServer) ListSelfGames(context.Context, *ListSelfGamesRequest) (*ListSelfGamesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGames not implemented") return nil, status.Error(codes.Unimplemented, "method ListSelfGames not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error) { func (UnimplementedGameAdminServiceServer) UpdateDiceConfig(context.Context, *UpdateDiceConfigRequest) (*DiceConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateDiceConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateDiceConfig not implemented")
} }
func (UnimplementedGameAdminServiceServer) GetSelfGameNewUserPolicy(context.Context, *GetSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) { func (UnimplementedGameAdminServiceServer) GetSelfGameNewUserPolicy(context.Context, *GetSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSelfGameNewUserPolicy not implemented") return nil, status.Error(codes.Unimplemented, "method GetSelfGameNewUserPolicy not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpdateSelfGameNewUserPolicy(context.Context, *UpdateSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) { func (UnimplementedGameAdminServiceServer) UpdateSelfGameNewUserPolicy(context.Context, *UpdateSelfGameNewUserPolicyRequest) (*SelfGameNewUserPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateSelfGameNewUserPolicy not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateSelfGameNewUserPolicy not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListSelfGameStakePools(context.Context, *ListSelfGameStakePoolsRequest) (*ListSelfGameStakePoolsResponse, error) { func (UnimplementedGameAdminServiceServer) ListSelfGameStakePools(context.Context, *ListSelfGameStakePoolsRequest) (*ListSelfGameStakePoolsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSelfGameStakePools not implemented") return nil, status.Error(codes.Unimplemented, "method ListSelfGameStakePools not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpdateSelfGameStakePool(context.Context, *UpdateSelfGameStakePoolRequest) (*SelfGameStakePoolResponse, error) { func (UnimplementedGameAdminServiceServer) UpdateSelfGameStakePool(context.Context, *UpdateSelfGameStakePoolRequest) (*SelfGameStakePoolResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateSelfGameStakePool not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateSelfGameStakePool not implemented")
} }
func (UnimplementedGameAdminServiceServer) AdjustSelfGameStakePool(context.Context, *AdjustSelfGameStakePoolRequest) (*AdjustSelfGameStakePoolResponse, error) { func (UnimplementedGameAdminServiceServer) AdjustSelfGameStakePool(context.Context, *AdjustSelfGameStakePoolRequest) (*AdjustSelfGameStakePoolResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdjustSelfGameStakePool not implemented") return nil, status.Error(codes.Unimplemented, "method AdjustSelfGameStakePool not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListDiceRobots(context.Context, *ListDiceRobotsRequest) (*ListDiceRobotsResponse, error) { func (UnimplementedGameAdminServiceServer) ListDiceRobots(context.Context, *ListDiceRobotsRequest) (*ListDiceRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListDiceRobots not implemented") return nil, status.Error(codes.Unimplemented, "method ListDiceRobots not implemented")
} }
func (UnimplementedGameAdminServiceServer) RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) { func (UnimplementedGameAdminServiceServer) RegisterDiceRobots(context.Context, *RegisterDiceRobotsRequest) (*RegisterDiceRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterDiceRobots not implemented") return nil, status.Error(codes.Unimplemented, "method RegisterDiceRobots not implemented")
} }
func (UnimplementedGameAdminServiceServer) SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) { func (UnimplementedGameAdminServiceServer) SetDiceRobotStatus(context.Context, *SetDiceRobotStatusRequest) (*DiceRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetDiceRobotStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetDiceRobotStatus not implemented")
} }
func (UnimplementedGameAdminServiceServer) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) { func (UnimplementedGameAdminServiceServer) DeleteDiceRobot(context.Context, *DeleteDiceRobotRequest) (*DeleteDiceRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteDiceRobot not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteDiceRobot not implemented")
} }
func (UnimplementedGameAdminServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { func (UnimplementedGameAdminServiceServer) GetRoomRPSConfig(context.Context, *GetRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomRPSConfig not implemented")
} }
func (UnimplementedGameAdminServiceServer) UpdateRoomRPSConfig(context.Context, *UpdateRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) { func (UnimplementedGameAdminServiceServer) UpdateRoomRPSConfig(context.Context, *UpdateRoomRPSConfigRequest) (*RoomRPSConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomRPSConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRoomRPSConfig not implemented")
} }
func (UnimplementedGameAdminServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) { func (UnimplementedGameAdminServiceServer) ListRoomRPSChallenges(context.Context, *ListRoomRPSChallengesRequest) (*ListRoomRPSChallengesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRPSChallenges not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomRPSChallenges not implemented")
} }
func (UnimplementedGameAdminServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAdminServiceServer) GetRoomRPSChallenge(context.Context, *GetRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRPSChallenge not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomRPSChallenge not implemented")
} }
func (UnimplementedGameAdminServiceServer) RetryRoomRPSSettlement(context.Context, *RetryRoomRPSSettlementRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAdminServiceServer) RetryRoomRPSSettlement(context.Context, *RetryRoomRPSSettlementRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RetryRoomRPSSettlement not implemented") return nil, status.Error(codes.Unimplemented, "method RetryRoomRPSSettlement not implemented")
} }
func (UnimplementedGameAdminServiceServer) ExpireRoomRPSChallenge(context.Context, *ExpireRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) { func (UnimplementedGameAdminServiceServer) ExpireRoomRPSChallenge(context.Context, *ExpireRoomRPSChallengeRequest) (*RoomRPSChallengeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpireRoomRPSChallenge not implemented") return nil, status.Error(codes.Unimplemented, "method ExpireRoomRPSChallenge not implemented")
} }
func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {} func (UnimplementedGameAdminServiceServer) mustEmbedUnimplementedGameAdminServiceServer() {}
func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {} func (UnimplementedGameAdminServiceServer) testEmbeddedByValue() {}
@ -1410,7 +1410,7 @@ type UnsafeGameAdminServiceServer interface {
} }
func RegisterGameAdminServiceServer(s grpc.ServiceRegistrar, srv GameAdminServiceServer) { func RegisterGameAdminServiceServer(s grpc.ServiceRegistrar, srv GameAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedGameAdminServiceServer was // If the following call panics, it indicates UnimplementedGameAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/luckygift/v1/luckygift.proto // source: proto/luckygift/v1/luckygift.proto
package luckygiftv1 package luckygiftv1
@ -102,16 +102,16 @@ type LuckyGiftServiceServer interface {
type UnimplementedLuckyGiftServiceServer struct{} type UnimplementedLuckyGiftServiceServer struct{}
func (UnimplementedLuckyGiftServiceServer) CheckLuckyGift(context.Context, *CheckLuckyGiftRequest) (*CheckLuckyGiftResponse, error) { func (UnimplementedLuckyGiftServiceServer) CheckLuckyGift(context.Context, *CheckLuckyGiftRequest) (*CheckLuckyGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckLuckyGift not implemented") return nil, status.Error(codes.Unimplemented, "method CheckLuckyGift not implemented")
} }
func (UnimplementedLuckyGiftServiceServer) ExecuteLuckyGiftDraw(context.Context, *ExecuteLuckyGiftDrawRequest) (*ExecuteLuckyGiftDrawResponse, error) { func (UnimplementedLuckyGiftServiceServer) ExecuteLuckyGiftDraw(context.Context, *ExecuteLuckyGiftDrawRequest) (*ExecuteLuckyGiftDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExecuteLuckyGiftDraw not implemented") return nil, status.Error(codes.Unimplemented, "method ExecuteLuckyGiftDraw not implemented")
} }
func (UnimplementedLuckyGiftServiceServer) BatchExecuteLuckyGiftDraw(context.Context, *BatchExecuteLuckyGiftDrawRequest) (*BatchExecuteLuckyGiftDrawResponse, error) { func (UnimplementedLuckyGiftServiceServer) BatchExecuteLuckyGiftDraw(context.Context, *BatchExecuteLuckyGiftDrawRequest) (*BatchExecuteLuckyGiftDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchExecuteLuckyGiftDraw not implemented") return nil, status.Error(codes.Unimplemented, "method BatchExecuteLuckyGiftDraw not implemented")
} }
func (UnimplementedLuckyGiftServiceServer) ExecuteExternalGiftDraw(context.Context, *ExternalGiftDrawRequest) (*ExternalGiftDrawResponse, error) { func (UnimplementedLuckyGiftServiceServer) ExecuteExternalGiftDraw(context.Context, *ExternalGiftDrawRequest) (*ExternalGiftDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExecuteExternalGiftDraw not implemented") return nil, status.Error(codes.Unimplemented, "method ExecuteExternalGiftDraw not implemented")
} }
func (UnimplementedLuckyGiftServiceServer) mustEmbedUnimplementedLuckyGiftServiceServer() {} func (UnimplementedLuckyGiftServiceServer) mustEmbedUnimplementedLuckyGiftServiceServer() {}
func (UnimplementedLuckyGiftServiceServer) testEmbeddedByValue() {} func (UnimplementedLuckyGiftServiceServer) testEmbeddedByValue() {}
@ -124,7 +124,7 @@ type UnsafeLuckyGiftServiceServer interface {
} }
func RegisterLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv LuckyGiftServiceServer) { func RegisterLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv LuckyGiftServiceServer) {
// If the following call pancis, it indicates UnimplementedLuckyGiftServiceServer was // If the following call panics, it indicates UnimplementedLuckyGiftServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -344,22 +344,22 @@ type AdminLuckyGiftServiceServer interface {
type UnimplementedAdminLuckyGiftServiceServer struct{} type UnimplementedAdminLuckyGiftServiceServer struct{}
func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftConfig(context.Context, *GetLuckyGiftConfigRequest) (*GetLuckyGiftConfigResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftConfig(context.Context, *GetLuckyGiftConfigRequest) (*GetLuckyGiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLuckyGiftConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetLuckyGiftConfig not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) UpsertLuckyGiftConfig(context.Context, *UpsertLuckyGiftConfigRequest) (*UpsertLuckyGiftConfigResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) UpsertLuckyGiftConfig(context.Context, *UpsertLuckyGiftConfigRequest) (*UpsertLuckyGiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertLuckyGiftConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpsertLuckyGiftConfig not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftConfigs(context.Context, *ListLuckyGiftConfigsRequest) (*ListLuckyGiftConfigsResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftConfigs(context.Context, *ListLuckyGiftConfigsRequest) (*ListLuckyGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLuckyGiftConfigs not implemented") return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftConfigs not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftDraws(context.Context, *ListLuckyGiftDrawsRequest) (*ListLuckyGiftDrawsResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftDraws(context.Context, *ListLuckyGiftDrawsRequest) (*ListLuckyGiftDrawsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLuckyGiftDraws not implemented") return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftDraws not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftDrawSummary(context.Context, *GetLuckyGiftDrawSummaryRequest) (*GetLuckyGiftDrawSummaryResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftDrawSummary(context.Context, *GetLuckyGiftDrawSummaryRequest) (*GetLuckyGiftDrawSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLuckyGiftDrawSummary not implemented") return nil, status.Error(codes.Unimplemented, "method GetLuckyGiftDrawSummary not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error) { func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLuckyGiftPoolBalances not implemented") return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftPoolBalances not implemented")
} }
func (UnimplementedAdminLuckyGiftServiceServer) mustEmbedUnimplementedAdminLuckyGiftServiceServer() {} func (UnimplementedAdminLuckyGiftServiceServer) mustEmbedUnimplementedAdminLuckyGiftServiceServer() {}
func (UnimplementedAdminLuckyGiftServiceServer) testEmbeddedByValue() {} func (UnimplementedAdminLuckyGiftServiceServer) testEmbeddedByValue() {}
@ -372,7 +372,7 @@ type UnsafeAdminLuckyGiftServiceServer interface {
} }
func RegisterAdminLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv AdminLuckyGiftServiceServer) { func RegisterAdminLuckyGiftServiceServer(s grpc.ServiceRegistrar, srv AdminLuckyGiftServiceServer) {
// If the following call pancis, it indicates UnimplementedAdminLuckyGiftServiceServer was // If the following call panics, it indicates UnimplementedAdminLuckyGiftServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.35.1 // protoc-gen-go v1.36.11
// protoc v7.35.0 // protoc v5.29.2
// source: proto/robot/v1/robot.proto // source: proto/robot/v1/robot.proto
package robotv1 package robotv1
@ -11,6 +11,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@ -22,15 +23,14 @@ const (
// RequestMeta 是 robot-service 内部 RPC 的租户和追踪上下文;不要把 app_code 放到业务字段里绕过统一鉴权链路。 // RequestMeta 是 robot-service 内部 RPC 的租户和追踪上下文;不要把 app_code 放到业务字段里绕过统一鉴权链路。
type RequestMeta struct { type RequestMeta struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"`
ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"`
SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"`
AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RequestMeta) Reset() { func (x *RequestMeta) Reset() {
@ -99,10 +99,7 @@ func (x *RequestMeta) GetAppCode() string {
} }
type Robot struct { type Robot struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
RobotScope string `protobuf:"bytes,2,opt,name=robot_scope,json=robotScope,proto3" json:"robot_scope,omitempty"` RobotScope string `protobuf:"bytes,2,opt,name=robot_scope,json=robotScope,proto3" json:"robot_scope,omitempty"`
GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
@ -114,6 +111,8 @@ type Robot struct {
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"` LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"`
UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"` UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *Robot) Reset() { func (x *Robot) Reset() {
@ -224,15 +223,14 @@ func (x *Robot) GetUsedCount() int64 {
} }
type ListGameRobotsRequest struct { type ListGameRobotsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"` Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ListGameRobotsRequest) Reset() { func (x *ListGameRobotsRequest) Reset() {
@ -301,13 +299,12 @@ func (x *ListGameRobotsRequest) GetCursor() string {
} }
type ListGameRobotsResponse struct { type ListGameRobotsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"` Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ListGameRobotsResponse) Reset() { func (x *ListGameRobotsResponse) Reset() {
@ -362,13 +359,12 @@ func (x *ListGameRobotsResponse) GetServerTimeMs() int64 {
} }
type RegisterGameRobotsRequest struct { type RegisterGameRobotsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RegisterGameRobotsRequest) Reset() { func (x *RegisterGameRobotsRequest) Reset() {
@ -423,12 +419,11 @@ func (x *RegisterGameRobotsRequest) GetUserIds() []int64 {
} }
type RegisterGameRobotsResponse struct { type RegisterGameRobotsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"` Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RegisterGameRobotsResponse) Reset() { func (x *RegisterGameRobotsResponse) Reset() {
@ -476,15 +471,14 @@ func (x *RegisterGameRobotsResponse) GetServerTimeMs() int64 {
} }
type PickGameRobotRequest struct { type PickGameRobotRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"` SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"` ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *PickGameRobotRequest) Reset() { func (x *PickGameRobotRequest) Reset() {
@ -553,12 +547,11 @@ func (x *PickGameRobotRequest) GetExcludeUserIds() []int64 {
} }
type GameRobotResponse struct { type GameRobotResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"` Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GameRobotResponse) Reset() { func (x *GameRobotResponse) Reset() {
@ -606,14 +599,13 @@ func (x *GameRobotResponse) GetServerTimeMs() int64 {
} }
type SetGameRobotStatusRequest struct { type SetGameRobotStatusRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *SetGameRobotStatusRequest) Reset() { func (x *SetGameRobotStatusRequest) Reset() {
@ -675,13 +667,12 @@ func (x *SetGameRobotStatusRequest) GetStatus() string {
} }
type DeleteGameRobotRequest struct { type DeleteGameRobotRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *DeleteGameRobotRequest) Reset() { func (x *DeleteGameRobotRequest) Reset() {
@ -736,12 +727,11 @@ func (x *DeleteGameRobotRequest) GetUserId() int64 {
} }
type DeleteGameRobotResponse struct { type DeleteGameRobotResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *DeleteGameRobotResponse) Reset() { func (x *DeleteGameRobotResponse) Reset() {
@ -789,15 +779,14 @@ func (x *DeleteGameRobotResponse) GetServerTimeMs() int64 {
} }
type ListRoomRobotsRequest struct { type ListRoomRobotsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"` Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ListRoomRobotsRequest) Reset() { func (x *ListRoomRobotsRequest) Reset() {
@ -866,13 +855,12 @@ func (x *ListRoomRobotsRequest) GetCursor() string {
} }
type ListRoomRobotsResponse struct { type ListRoomRobotsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"` Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *ListRoomRobotsResponse) Reset() { func (x *ListRoomRobotsResponse) Reset() {
@ -927,13 +915,12 @@ func (x *ListRoomRobotsResponse) GetServerTimeMs() int64 {
} }
type RegisterRoomRobotsRequest struct { type RegisterRoomRobotsRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RegisterRoomRobotsRequest) Reset() { func (x *RegisterRoomRobotsRequest) Reset() {
@ -988,12 +975,11 @@ func (x *RegisterRoomRobotsRequest) GetUserIds() []int64 {
} }
type RegisterRoomRobotsResponse struct { type RegisterRoomRobotsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"` Robots []*Robot `protobuf:"bytes,1,rep,name=robots,proto3" json:"robots,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RegisterRoomRobotsResponse) Reset() { func (x *RegisterRoomRobotsResponse) Reset() {
@ -1041,14 +1027,13 @@ func (x *RegisterRoomRobotsResponse) GetServerTimeMs() int64 {
} }
type SetRoomRobotStatusRequest struct { type SetRoomRobotStatusRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *SetRoomRobotStatusRequest) Reset() { func (x *SetRoomRobotStatusRequest) Reset() {
@ -1110,13 +1095,12 @@ func (x *SetRoomRobotStatusRequest) GetStatus() string {
} }
type DeleteRoomRobotRequest struct { type DeleteRoomRobotRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *DeleteRoomRobotRequest) Reset() { func (x *DeleteRoomRobotRequest) Reset() {
@ -1171,12 +1155,11 @@ func (x *DeleteRoomRobotRequest) GetUserId() int64 {
} }
type DeleteRoomRobotResponse struct { type DeleteRoomRobotResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"` Deleted bool `protobuf:"varint,1,opt,name=deleted,proto3" json:"deleted,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *DeleteRoomRobotResponse) Reset() { func (x *DeleteRoomRobotResponse) Reset() {
@ -1224,14 +1207,13 @@ func (x *DeleteRoomRobotResponse) GetServerTimeMs() int64 {
} }
type PickRoomRobotRequest struct { type PickRoomRobotRequest struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` RoomScene string `protobuf:"bytes,2,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"` SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *PickRoomRobotRequest) Reset() { func (x *PickRoomRobotRequest) Reset() {
@ -1293,12 +1275,11 @@ func (x *PickRoomRobotRequest) GetSelectedAtMs() int64 {
} }
type RoomRobotResponse struct { type RoomRobotResponse struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"` Robot *Robot `protobuf:"bytes,1,opt,name=robot,proto3" json:"robot,omitempty"`
ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RoomRobotResponse) Reset() { func (x *RoomRobotResponse) Reset() {
@ -1347,276 +1328,136 @@ func (x *RoomRobotResponse) GetServerTimeMs() int64 {
var File_proto_robot_v1_robot_proto protoreflect.FileDescriptor var File_proto_robot_v1_robot_proto protoreflect.FileDescriptor
var file_proto_robot_v1_robot_proto_rawDesc = []byte{ const file_proto_robot_v1_robot_proto_rawDesc = "" +
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2f, 0x76, 0x31, "\n" +
0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x68, 0x79, "\x1aproto/robot/v1/robot.proto\x12\x0ehyapp.robot.v1\"\xa1\x01\n" +
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x22, 0xa1, 0x01, 0x0a, "\vRequestMeta\x12\x1d\n" +
0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, "\n" +
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, "request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" +
0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63, "\x06caller\x18\x02 \x01(\tR\x06caller\x12\"\n" +
0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6c, "\ractor_user_id\x18\x03 \x01(\x03R\vactorUserId\x12\x1c\n" +
0x6c, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, "\n" +
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, "sent_at_ms\x18\x04 \x01(\x03R\bsentAtMs\x12\x19\n" +
0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x74, 0x5f, "\bapp_code\x18\x05 \x01(\tR\aappCode\"\xe9\x02\n" +
0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x6e, "\x05Robot\x12\x19\n" +
0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1f\n" +
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, "\vrobot_scope\x18\x02 \x01(\tR\n" +
0x22, 0xe9, 0x02, 0x0a, 0x05, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x70, "robotScope\x12\x17\n" +
0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x70, "\agame_id\x18\x03 \x01(\tR\x06gameId\x12\x1d\n" +
0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x73, "\n" +
0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x62, 0x6f, "room_scene\x18\x04 \x01(\tR\troomScene\x12\x17\n" +
0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, "\auser_id\x18\x05 \x01(\x03R\x06userId\x12\x16\n" +
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, "\x06status\x18\x06 \x01(\tR\x06status\x12-\n" +
0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x04, 0x20, "\x13created_by_admin_id\x18\a \x01(\x03R\x10createdByAdminId\x12\"\n" +
0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, "\rcreated_at_ms\x18\b \x01(\x03R\vcreatedAtMs\x12\"\n" +
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, "\rupdated_at_ms\x18\t \x01(\x03R\vupdatedAtMs\x12%\n" +
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, "\x0flast_used_at_ms\x18\n" +
0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, " \x01(\x03R\flastUsedAtMs\x12\x1d\n" +
0x2d, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x61, 0x64, "\n" +
0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, 0x72, "used_count\x18\v \x01(\x03R\tusedCount\"\xae\x01\n" +
0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x22, "\x15ListGameRobotsRequest\x12/\n" +
0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x16\n" +
0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, "\x06status\x18\x03 \x01(\tR\x06status\x12\x1b\n" +
0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x16\n" +
0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, "\x06cursor\x18\x05 \x01(\tR\x06cursor\"\x8e\x01\n" +
0x73, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, "\x16ListGameRobotsResponse\x12-\n" +
0x0c, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1d, 0x0a, "\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12\x1f\n" +
0x0a, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, "\vnext_cursor\x18\x02 \x01(\tR\n" +
0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xae, 0x01, 0x0a, "nextCursor\x12$\n" +
0x15, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x80\x01\n" +
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, "\x19RegisterGameRobotsRequest\x12/\n" +
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x19\n" +
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, "\buser_ids\x18\x03 \x03(\x03R\auserIds\"q\n" +
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, "\x1aRegisterGameRobotsResponse\x12-\n" +
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, "\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12$\n" +
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xcb\x01\n" +
0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, "\x14PickGameRobotRequest\x12/\n" +
0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x22, 0x8e, 0x01, "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x19\n" +
0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, "\bmatch_id\x18\x03 \x01(\tR\amatchId\x12$\n" +
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x6f, 0x62, 0x6f, "\x0eselected_at_ms\x18\x04 \x01(\x03R\fselectedAtMs\x12(\n" +
0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, "\x10exclude_user_ids\x18\x05 \x03(\x03R\x0eexcludeUserIds\"f\n" +
0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, "\x11GameRobotResponse\x12+\n" +
0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x78, 0x74, 0x5f, "\x05robot\x18\x01 \x01(\v2\x15.hyapp.robot.v1.RobotR\x05robot\x12$\n" +
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x96\x01\n" +
0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, "\x19SetGameRobotStatusRequest\x12/\n" +
0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x80, "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x17\n" +
0x01, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61, 0x6d, 0x65, 0x52, "\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x16\n" +
0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, "\x06status\x18\x04 \x01(\tR\x06status\"{\n" +
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, "\x16DeleteGameRobotRequest\x12/\n" +
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x17\n" +
0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, "\agame_id\x18\x02 \x01(\tR\x06gameId\x12\x17\n" +
0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, "\auser_id\x18\x03 \x01(\x03R\x06userId\"Y\n" +
0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, "\x17DeleteGameRobotResponse\x12\x18\n" +
0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, "\adeleted\x18\x01 \x01(\bR\adeleted\x12$\n" +
0x73, 0x22, 0x71, 0x0a, 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61, 0x6d, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xb4\x01\n" +
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, "\x15ListRoomRobotsRequest\x12/\n" +
0x2d, 0x0a, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, "\n" +
0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x24, "room_scene\x18\x02 \x01(\tR\troomScene\x12\x16\n" +
0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, "\x06status\x18\x03 \x01(\tR\x06status\x12\x1b\n" +
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12\x16\n" +
0x6d, 0x65, 0x4d, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x14, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d, "\x06cursor\x18\x05 \x01(\tR\x06cursor\"\x8e\x01\n" +
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, "\x16ListRoomRobotsResponse\x12-\n" +
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, "\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12\x1f\n" +
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, "\vnext_cursor\x18\x02 \x01(\tR\n" +
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, "nextCursor\x12$\n" +
0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x86\x01\n" +
0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68, "\x19RegisterRoomRobotsRequest\x12/\n" +
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, "\n" +
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65, "room_scene\x18\x02 \x01(\tR\troomScene\x12\x19\n" +
0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x63, 0x6c, "\buser_ids\x18\x03 \x03(\x03R\auserIds\"q\n" +
0x75, 0x64, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, "\x1aRegisterRoomRobotsResponse\x12-\n" +
0x28, 0x03, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73, 0x65, 0x72, 0x49, "\x06robots\x18\x01 \x03(\v2\x15.hyapp.robot.v1.RobotR\x06robots\x12$\n" +
0x64, 0x73, 0x22, 0x66, 0x0a, 0x11, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\x9c\x01\n" +
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74, "\x19SetRoomRobotStatusRequest\x12/\n" +
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x05, 0x72, "\n" +
0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, "room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, "\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x16\n" +
0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x19, 0x53, "\x06status\x18\x04 \x01(\tR\x06status\"\x81\x01\n" +
0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, "\x16DeleteRoomRobotRequest\x12/\n" +
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, "\n" +
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, "room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x61, 0x6d, "\auser_id\x18\x03 \x01(\x03R\x06userId\"Y\n" +
0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x61, 0x6d, 0x65, "\x17DeleteRoomRobotResponse\x12\x18\n" +
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, "\adeleted\x18\x01 \x01(\bR\adeleted\x12$\n" +
0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xa5\x01\n" +
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, "\x14PickRoomRobotRequest\x12/\n" +
0x74, 0x75, 0x73, 0x22, 0x7b, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, "\x04meta\x18\x01 \x01(\v2\x1b.hyapp.robot.v1.RequestMetaR\x04meta\x12\x1d\n" +
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, "\n" +
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, "room_scene\x18\x02 \x01(\tR\troomScene\x12\x17\n" +
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, "\aroom_id\x18\x03 \x01(\tR\x06roomId\x12$\n" +
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, "\x0eselected_at_ms\x18\x04 \x01(\x03R\fselectedAtMs\"f\n" +
0x0a, 0x07, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, "\x11RoomRobotResponse\x12+\n" +
0x06, 0x67, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, "\x05robot\x18\x01 \x01(\v2\x15.hyapp.robot.v1.RobotR\x05robot\x12$\n" +
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs2\x82\x04\n" +
0x22, 0x59, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, "\x10GameRobotService\x12_\n" +
0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, "\x0eListGameRobots\x12%.hyapp.robot.v1.ListGameRobotsRequest\x1a&.hyapp.robot.v1.ListGameRobotsResponse\x12k\n" +
0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, "\x12RegisterGameRobots\x12).hyapp.robot.v1.RegisterGameRobotsRequest\x1a*.hyapp.robot.v1.RegisterGameRobotsResponse\x12X\n" +
0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, "\rPickGameRobot\x12$.hyapp.robot.v1.PickGameRobotRequest\x1a!.hyapp.robot.v1.GameRobotResponse\x12b\n" +
0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, "\x12SetGameRobotStatus\x12).hyapp.robot.v1.SetGameRobotStatusRequest\x1a!.hyapp.robot.v1.GameRobotResponse\x12b\n" +
0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x15, "\x0fDeleteGameRobot\x12&.hyapp.robot.v1.DeleteGameRobotRequest\x1a'.hyapp.robot.v1.DeleteGameRobotResponse2\x82\x04\n" +
0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, "\x10RoomRobotService\x12_\n" +
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, "\x0eListRoomRobots\x12%.hyapp.robot.v1.ListRoomRobotsRequest\x1a&.hyapp.robot.v1.ListRoomRobotsResponse\x12k\n" +
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, "\x12RegisterRoomRobots\x12).hyapp.robot.v1.RegisterRoomRobotsRequest\x1a*.hyapp.robot.v1.RegisterRoomRobotsResponse\x12X\n" +
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, "\rPickRoomRobot\x12$.hyapp.robot.v1.PickRoomRobotRequest\x1a!.hyapp.robot.v1.RoomRobotResponse\x12b\n" +
0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, "\x12SetRoomRobotStatus\x12).hyapp.robot.v1.SetRoomRobotStatusRequest\x1a!.hyapp.robot.v1.RoomRobotResponse\x12b\n" +
0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, "\x0fDeleteRoomRobot\x12&.hyapp.robot.v1.DeleteRoomRobotRequest\x1a'.hyapp.robot.v1.DeleteRoomRobotResponseB(Z&hyapp.local/api/proto/robot/v1;robotv1b\x06proto3"
0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a,
0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05,
0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75,
0x72, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73,
0x6f, 0x72, 0x22, 0x8e, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52,
0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a,
0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e,
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x6f, 0x62, 0x6f, 0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x24, 0x0a,
0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18,
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d,
0x65, 0x4d, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e,
0x65, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0x71, 0x0a, 0x1a,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x52, 0x06, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22,
0x9c, 0x01, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a,
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d,
0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a,
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x81,
0x01, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f,
0x6f, 0x6d, 0x5f, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
0x49, 0x64, 0x22, 0x59, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d,
0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xa5, 0x01,
0x0a, 0x14, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74,
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x5f,
0x73, 0x63, 0x65, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x6f,
0x6d, 0x53, 0x63, 0x65, 0x6e, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12,
0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65,
0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x66, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x52, 0x05, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x32, 0x82, 0x04,
0x0a, 0x10, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47,
0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62,
0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x61,
0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x58, 0x0a, 0x0d, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x50, 0x69, 0x63, 0x6b, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x12, 0x53, 0x65,
0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79,
0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x6d,
0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62,
0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62,
0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x47, 0x61, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x32, 0x82, 0x04, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52,
0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x12, 0x29,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f,
0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x6d,
0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68,
0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f,
0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x62, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f,
0x62, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76,
0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f,
0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72,
0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f,
0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27,
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2e, 0x76, 0x31, 0x2e,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x68, 0x79, 0x61, 0x70, 0x70,
0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x76,
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_proto_robot_v1_robot_proto_rawDescOnce sync.Once file_proto_robot_v1_robot_proto_rawDescOnce sync.Once
file_proto_robot_v1_robot_proto_rawDescData = file_proto_robot_v1_robot_proto_rawDesc file_proto_robot_v1_robot_proto_rawDescData []byte
) )
func file_proto_robot_v1_robot_proto_rawDescGZIP() []byte { func file_proto_robot_v1_robot_proto_rawDescGZIP() []byte {
file_proto_robot_v1_robot_proto_rawDescOnce.Do(func() { file_proto_robot_v1_robot_proto_rawDescOnce.Do(func() {
file_proto_robot_v1_robot_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_robot_v1_robot_proto_rawDescData) file_proto_robot_v1_robot_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_robot_v1_robot_proto_rawDesc), len(file_proto_robot_v1_robot_proto_rawDesc)))
}) })
return file_proto_robot_v1_robot_proto_rawDescData return file_proto_robot_v1_robot_proto_rawDescData
} }
@ -1697,7 +1538,7 @@ func file_proto_robot_v1_robot_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_robot_v1_robot_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_robot_v1_robot_proto_rawDesc), len(file_proto_robot_v1_robot_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 20, NumMessages: 20,
NumExtensions: 0, NumExtensions: 0,
@ -1708,7 +1549,6 @@ func file_proto_robot_v1_robot_proto_init() {
MessageInfos: file_proto_robot_v1_robot_proto_msgTypes, MessageInfos: file_proto_robot_v1_robot_proto_msgTypes,
}.Build() }.Build()
File_proto_robot_v1_robot_proto = out.File File_proto_robot_v1_robot_proto = out.File
file_proto_robot_v1_robot_proto_rawDesc = nil
file_proto_robot_v1_robot_proto_goTypes = nil file_proto_robot_v1_robot_proto_goTypes = nil
file_proto_robot_v1_robot_proto_depIdxs = nil file_proto_robot_v1_robot_proto_depIdxs = nil
} }

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/robot/v1/robot.proto // source: proto/robot/v1/robot.proto
package robotv1 package robotv1
@ -115,19 +115,19 @@ type GameRobotServiceServer interface {
type UnimplementedGameRobotServiceServer struct{} type UnimplementedGameRobotServiceServer struct{}
func (UnimplementedGameRobotServiceServer) ListGameRobots(context.Context, *ListGameRobotsRequest) (*ListGameRobotsResponse, error) { func (UnimplementedGameRobotServiceServer) ListGameRobots(context.Context, *ListGameRobotsRequest) (*ListGameRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGameRobots not implemented") return nil, status.Error(codes.Unimplemented, "method ListGameRobots not implemented")
} }
func (UnimplementedGameRobotServiceServer) RegisterGameRobots(context.Context, *RegisterGameRobotsRequest) (*RegisterGameRobotsResponse, error) { func (UnimplementedGameRobotServiceServer) RegisterGameRobots(context.Context, *RegisterGameRobotsRequest) (*RegisterGameRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterGameRobots not implemented") return nil, status.Error(codes.Unimplemented, "method RegisterGameRobots not implemented")
} }
func (UnimplementedGameRobotServiceServer) PickGameRobot(context.Context, *PickGameRobotRequest) (*GameRobotResponse, error) { func (UnimplementedGameRobotServiceServer) PickGameRobot(context.Context, *PickGameRobotRequest) (*GameRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PickGameRobot not implemented") return nil, status.Error(codes.Unimplemented, "method PickGameRobot not implemented")
} }
func (UnimplementedGameRobotServiceServer) SetGameRobotStatus(context.Context, *SetGameRobotStatusRequest) (*GameRobotResponse, error) { func (UnimplementedGameRobotServiceServer) SetGameRobotStatus(context.Context, *SetGameRobotStatusRequest) (*GameRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGameRobotStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetGameRobotStatus not implemented")
} }
func (UnimplementedGameRobotServiceServer) DeleteGameRobot(context.Context, *DeleteGameRobotRequest) (*DeleteGameRobotResponse, error) { func (UnimplementedGameRobotServiceServer) DeleteGameRobot(context.Context, *DeleteGameRobotRequest) (*DeleteGameRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteGameRobot not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteGameRobot not implemented")
} }
func (UnimplementedGameRobotServiceServer) mustEmbedUnimplementedGameRobotServiceServer() {} func (UnimplementedGameRobotServiceServer) mustEmbedUnimplementedGameRobotServiceServer() {}
func (UnimplementedGameRobotServiceServer) testEmbeddedByValue() {} func (UnimplementedGameRobotServiceServer) testEmbeddedByValue() {}
@ -140,7 +140,7 @@ type UnsafeGameRobotServiceServer interface {
} }
func RegisterGameRobotServiceServer(s grpc.ServiceRegistrar, srv GameRobotServiceServer) { func RegisterGameRobotServiceServer(s grpc.ServiceRegistrar, srv GameRobotServiceServer) {
// If the following call pancis, it indicates UnimplementedGameRobotServiceServer was // If the following call panics, it indicates UnimplementedGameRobotServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -369,19 +369,19 @@ type RoomRobotServiceServer interface {
type UnimplementedRoomRobotServiceServer struct{} type UnimplementedRoomRobotServiceServer struct{}
func (UnimplementedRoomRobotServiceServer) ListRoomRobots(context.Context, *ListRoomRobotsRequest) (*ListRoomRobotsResponse, error) { func (UnimplementedRoomRobotServiceServer) ListRoomRobots(context.Context, *ListRoomRobotsRequest) (*ListRoomRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomRobots not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomRobots not implemented")
} }
func (UnimplementedRoomRobotServiceServer) RegisterRoomRobots(context.Context, *RegisterRoomRobotsRequest) (*RegisterRoomRobotsResponse, error) { func (UnimplementedRoomRobotServiceServer) RegisterRoomRobots(context.Context, *RegisterRoomRobotsRequest) (*RegisterRoomRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterRoomRobots not implemented") return nil, status.Error(codes.Unimplemented, "method RegisterRoomRobots not implemented")
} }
func (UnimplementedRoomRobotServiceServer) PickRoomRobot(context.Context, *PickRoomRobotRequest) (*RoomRobotResponse, error) { func (UnimplementedRoomRobotServiceServer) PickRoomRobot(context.Context, *PickRoomRobotRequest) (*RoomRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PickRoomRobot not implemented") return nil, status.Error(codes.Unimplemented, "method PickRoomRobot not implemented")
} }
func (UnimplementedRoomRobotServiceServer) SetRoomRobotStatus(context.Context, *SetRoomRobotStatusRequest) (*RoomRobotResponse, error) { func (UnimplementedRoomRobotServiceServer) SetRoomRobotStatus(context.Context, *SetRoomRobotStatusRequest) (*RoomRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomRobotStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetRoomRobotStatus not implemented")
} }
func (UnimplementedRoomRobotServiceServer) DeleteRoomRobot(context.Context, *DeleteRoomRobotRequest) (*DeleteRoomRobotResponse, error) { func (UnimplementedRoomRobotServiceServer) DeleteRoomRobot(context.Context, *DeleteRoomRobotRequest) (*DeleteRoomRobotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteRoomRobot not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteRoomRobot not implemented")
} }
func (UnimplementedRoomRobotServiceServer) mustEmbedUnimplementedRoomRobotServiceServer() {} func (UnimplementedRoomRobotServiceServer) mustEmbedUnimplementedRoomRobotServiceServer() {}
func (UnimplementedRoomRobotServiceServer) testEmbeddedByValue() {} func (UnimplementedRoomRobotServiceServer) testEmbeddedByValue() {}
@ -394,7 +394,7 @@ type UnsafeRoomRobotServiceServer interface {
} }
func RegisterRoomRobotServiceServer(s grpc.ServiceRegistrar, srv RoomRobotServiceServer) { func RegisterRoomRobotServiceServer(s grpc.ServiceRegistrar, srv RoomRobotServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomRobotServiceServer was // If the following call panics, it indicates UnimplementedRoomRobotServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/room/v1/room.proto // source: proto/room/v1/room.proto
package roomv1 package roomv1
@ -522,112 +522,112 @@ type RoomCommandServiceServer interface {
type UnimplementedRoomCommandServiceServer struct{} type UnimplementedRoomCommandServiceServer struct{}
func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) CreateRoom(context.Context, *CreateRoomRequest) (*CreateRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRoom not implemented") return nil, status.Error(codes.Unimplemented, "method CreateRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) { func (UnimplementedRoomCommandServiceServer) UpdateRoomProfile(context.Context, *UpdateRoomProfileRequest) (*UpdateRoomProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoomProfile not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRoomProfile not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error) { func (UnimplementedRoomCommandServiceServer) SaveRoomBackground(context.Context, *SaveRoomBackgroundRequest) (*SaveRoomBackgroundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SaveRoomBackground not implemented") return nil, status.Error(codes.Unimplemented, "method SaveRoomBackground not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error) { func (UnimplementedRoomCommandServiceServer) SetRoomBackground(context.Context, *SetRoomBackgroundRequest) (*SetRoomBackgroundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomBackground not implemented") return nil, status.Error(codes.Unimplemented, "method SetRoomBackground not implemented")
} }
func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) JoinRoom(context.Context, *JoinRoomRequest) (*JoinRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method JoinRoom not implemented") return nil, status.Error(codes.Unimplemented, "method JoinRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error) { func (UnimplementedRoomCommandServiceServer) RoomHeartbeat(context.Context, *RoomHeartbeatRequest) (*RoomHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RoomHeartbeat not implemented") return nil, status.Error(codes.Unimplemented, "method RoomHeartbeat not implemented")
} }
func (UnimplementedRoomCommandServiceServer) LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) LeaveRoom(context.Context, *LeaveRoomRequest) (*LeaveRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LeaveRoom not implemented") return nil, status.Error(codes.Unimplemented, "method LeaveRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) CloseRoom(context.Context, *CloseRoomRequest) (*CloseRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) CloseRoom(context.Context, *CloseRoomRequest) (*CloseRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseRoom not implemented") return nil, status.Error(codes.Unimplemented, "method CloseRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoom(context.Context, *AdminUpdateRoomRequest) (*AdminUpdateRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminUpdateRoom(context.Context, *AdminUpdateRoomRequest) (*AdminUpdateRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoom not implemented") return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminDeleteRoom(context.Context, *AdminDeleteRoomRequest) (*AdminDeleteRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminDeleteRoom(context.Context, *AdminDeleteRoomRequest) (*AdminDeleteRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminDeleteRoom not implemented") return nil, status.Error(codes.Unimplemented, "method AdminDeleteRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomRocketConfig(context.Context, *AdminUpdateRoomRocketConfigRequest) (*AdminUpdateRoomRocketConfigResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomRocketConfig(context.Context, *AdminUpdateRoomRocketConfigRequest) (*AdminUpdateRoomRocketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomRocketConfig not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomSeatConfig(context.Context, *AdminUpdateRoomSeatConfigRequest) (*AdminUpdateRoomSeatConfigResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminUpdateRoomSeatConfig(context.Context, *AdminUpdateRoomSeatConfigRequest) (*AdminUpdateRoomSeatConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminUpdateRoomSeatConfig not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminUpdateHumanRoomRobotConfig(context.Context, *AdminUpdateHumanRoomRobotConfigRequest) (*AdminUpdateHumanRoomRobotConfigResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminUpdateHumanRoomRobotConfig(context.Context, *AdminUpdateHumanRoomRobotConfigRequest) (*AdminUpdateHumanRoomRobotConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUpdateHumanRoomRobotConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminUpdateHumanRoomRobotConfig not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminCreateRoomPin(context.Context, *AdminCreateRoomPinRequest) (*AdminCreateRoomPinResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminCreateRoomPin(context.Context, *AdminCreateRoomPinRequest) (*AdminCreateRoomPinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreateRoomPin not implemented") return nil, status.Error(codes.Unimplemented, "method AdminCreateRoomPin not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminCancelRoomPin(context.Context, *AdminCancelRoomPinRequest) (*AdminCancelRoomPinResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminCancelRoomPin(context.Context, *AdminCancelRoomPinRequest) (*AdminCancelRoomPinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCancelRoomPin not implemented") return nil, status.Error(codes.Unimplemented, "method AdminCancelRoomPin not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminCreateRobotRoom(context.Context, *AdminCreateRobotRoomRequest) (*AdminCreateRobotRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminCreateRobotRoom(context.Context, *AdminCreateRobotRoomRequest) (*AdminCreateRobotRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreateRobotRoom not implemented") return nil, status.Error(codes.Unimplemented, "method AdminCreateRobotRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminSetRobotRoomStatus(context.Context, *AdminSetRobotRoomStatusRequest) (*AdminSetRobotRoomStatusResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminSetRobotRoomStatus(context.Context, *AdminSetRobotRoomStatusRequest) (*AdminSetRobotRoomStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminSetRobotRoomStatus not implemented") return nil, status.Error(codes.Unimplemented, "method AdminSetRobotRoomStatus not implemented")
} }
func (UnimplementedRoomCommandServiceServer) AdminRenameOwnerCountryCode(context.Context, *AdminRenameOwnerCountryCodeRequest) (*AdminRenameOwnerCountryCodeResponse, error) { func (UnimplementedRoomCommandServiceServer) AdminRenameOwnerCountryCode(context.Context, *AdminRenameOwnerCountryCodeRequest) (*AdminRenameOwnerCountryCodeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminRenameOwnerCountryCode not implemented") return nil, status.Error(codes.Unimplemented, "method AdminRenameOwnerCountryCode not implemented")
} }
func (UnimplementedRoomCommandServiceServer) MicUp(context.Context, *MicUpRequest) (*MicUpResponse, error) { func (UnimplementedRoomCommandServiceServer) MicUp(context.Context, *MicUpRequest) (*MicUpResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicUp not implemented") return nil, status.Error(codes.Unimplemented, "method MicUp not implemented")
} }
func (UnimplementedRoomCommandServiceServer) MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) { func (UnimplementedRoomCommandServiceServer) MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicDown not implemented") return nil, status.Error(codes.Unimplemented, "method MicDown not implemented")
} }
func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) { func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeMicSeat not implemented") return nil, status.Error(codes.Unimplemented, "method ChangeMicSeat not implemented")
} }
func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) { func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmMicPublishing not implemented") return nil, status.Error(codes.Unimplemented, "method ConfirmMicPublishing not implemented")
} }
func (UnimplementedRoomCommandServiceServer) MicHeartbeat(context.Context, *MicHeartbeatRequest) (*MicHeartbeatResponse, error) { func (UnimplementedRoomCommandServiceServer) MicHeartbeat(context.Context, *MicHeartbeatRequest) (*MicHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MicHeartbeat not implemented") return nil, status.Error(codes.Unimplemented, "method MicHeartbeat not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) { func (UnimplementedRoomCommandServiceServer) SetMicMute(context.Context, *SetMicMuteRequest) (*SetMicMuteResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMicMute not implemented") return nil, status.Error(codes.Unimplemented, "method SetMicMute not implemented")
} }
func (UnimplementedRoomCommandServiceServer) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) { func (UnimplementedRoomCommandServiceServer) ApplyRTCEvent(context.Context, *ApplyRTCEventRequest) (*ApplyRTCEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyRTCEvent not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyRTCEvent not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) { func (UnimplementedRoomCommandServiceServer) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetMicSeatLock not implemented") return nil, status.Error(codes.Unimplemented, "method SetMicSeatLock not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) { func (UnimplementedRoomCommandServiceServer) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetChatEnabled not implemented") return nil, status.Error(codes.Unimplemented, "method SetChatEnabled not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetRoomPassword(context.Context, *SetRoomPasswordRequest) (*SetRoomPasswordResponse, error) { func (UnimplementedRoomCommandServiceServer) SetRoomPassword(context.Context, *SetRoomPasswordRequest) (*SetRoomPasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomPassword not implemented") return nil, status.Error(codes.Unimplemented, "method SetRoomPassword not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) { func (UnimplementedRoomCommandServiceServer) SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetRoomAdmin not implemented") return nil, status.Error(codes.Unimplemented, "method SetRoomAdmin not implemented")
} }
func (UnimplementedRoomCommandServiceServer) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) { func (UnimplementedRoomCommandServiceServer) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuteUser not implemented") return nil, status.Error(codes.Unimplemented, "method MuteUser not implemented")
} }
func (UnimplementedRoomCommandServiceServer) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) { func (UnimplementedRoomCommandServiceServer) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method KickUser not implemented") return nil, status.Error(codes.Unimplemented, "method KickUser not implemented")
} }
func (UnimplementedRoomCommandServiceServer) UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) { func (UnimplementedRoomCommandServiceServer) UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnbanUser not implemented") return nil, status.Error(codes.Unimplemented, "method UnbanUser not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SystemEvictUser(context.Context, *SystemEvictUserRequest) (*SystemEvictUserResponse, error) { func (UnimplementedRoomCommandServiceServer) SystemEvictUser(context.Context, *SystemEvictUserRequest) (*SystemEvictUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SystemEvictUser not implemented") return nil, status.Error(codes.Unimplemented, "method SystemEvictUser not implemented")
} }
func (UnimplementedRoomCommandServiceServer) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) { func (UnimplementedRoomCommandServiceServer) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendGift not implemented") return nil, status.Error(codes.Unimplemented, "method SendGift not implemented")
} }
func (UnimplementedRoomCommandServiceServer) FollowRoom(context.Context, *FollowRoomRequest) (*FollowRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) FollowRoom(context.Context, *FollowRoomRequest) (*FollowRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FollowRoom not implemented") return nil, status.Error(codes.Unimplemented, "method FollowRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) UnfollowRoom(context.Context, *UnfollowRoomRequest) (*UnfollowRoomResponse, error) { func (UnimplementedRoomCommandServiceServer) UnfollowRoom(context.Context, *UnfollowRoomRequest) (*UnfollowRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnfollowRoom not implemented") return nil, status.Error(codes.Unimplemented, "method UnfollowRoom not implemented")
} }
func (UnimplementedRoomCommandServiceServer) mustEmbedUnimplementedRoomCommandServiceServer() {} func (UnimplementedRoomCommandServiceServer) mustEmbedUnimplementedRoomCommandServiceServer() {}
func (UnimplementedRoomCommandServiceServer) testEmbeddedByValue() {} func (UnimplementedRoomCommandServiceServer) testEmbeddedByValue() {}
@ -640,7 +640,7 @@ type UnsafeRoomCommandServiceServer interface {
} }
func RegisterRoomCommandServiceServer(s grpc.ServiceRegistrar, srv RoomCommandServiceServer) { func RegisterRoomCommandServiceServer(s grpc.ServiceRegistrar, srv RoomCommandServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomCommandServiceServer was // If the following call panics, it indicates UnimplementedRoomCommandServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1529,13 +1529,13 @@ type RoomGuardServiceServer interface {
type UnimplementedRoomGuardServiceServer struct{} type UnimplementedRoomGuardServiceServer struct{}
func (UnimplementedRoomGuardServiceServer) CheckSpeakPermission(context.Context, *CheckSpeakPermissionRequest) (*CheckSpeakPermissionResponse, error) { func (UnimplementedRoomGuardServiceServer) CheckSpeakPermission(context.Context, *CheckSpeakPermissionRequest) (*CheckSpeakPermissionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckSpeakPermission not implemented") return nil, status.Error(codes.Unimplemented, "method CheckSpeakPermission not implemented")
} }
func (UnimplementedRoomGuardServiceServer) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) { func (UnimplementedRoomGuardServiceServer) VerifyRoomPresence(context.Context, *VerifyRoomPresenceRequest) (*VerifyRoomPresenceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyRoomPresence not implemented") return nil, status.Error(codes.Unimplemented, "method VerifyRoomPresence not implemented")
} }
func (UnimplementedRoomGuardServiceServer) ResolveRoomAppCode(context.Context, *ResolveRoomAppCodeRequest) (*ResolveRoomAppCodeResponse, error) { func (UnimplementedRoomGuardServiceServer) ResolveRoomAppCode(context.Context, *ResolveRoomAppCodeRequest) (*ResolveRoomAppCodeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResolveRoomAppCode not implemented") return nil, status.Error(codes.Unimplemented, "method ResolveRoomAppCode not implemented")
} }
func (UnimplementedRoomGuardServiceServer) mustEmbedUnimplementedRoomGuardServiceServer() {} func (UnimplementedRoomGuardServiceServer) mustEmbedUnimplementedRoomGuardServiceServer() {}
func (UnimplementedRoomGuardServiceServer) testEmbeddedByValue() {} func (UnimplementedRoomGuardServiceServer) testEmbeddedByValue() {}
@ -1548,7 +1548,7 @@ type UnsafeRoomGuardServiceServer interface {
} }
func RegisterRoomGuardServiceServer(s grpc.ServiceRegistrar, srv RoomGuardServiceServer) { func RegisterRoomGuardServiceServer(s grpc.ServiceRegistrar, srv RoomGuardServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomGuardServiceServer was // If the following call panics, it indicates UnimplementedRoomGuardServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1932,64 +1932,64 @@ type RoomQueryServiceServer interface {
type UnimplementedRoomQueryServiceServer struct{} type UnimplementedRoomQueryServiceServer struct{}
func (UnimplementedRoomQueryServiceServer) AdminListRooms(context.Context, *AdminListRoomsRequest) (*AdminListRoomsResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminListRooms(context.Context, *AdminListRoomsRequest) (*AdminListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminListRooms not implemented") return nil, status.Error(codes.Unimplemented, "method AdminListRooms not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminGetRoom(context.Context, *AdminGetRoomRequest) (*AdminGetRoomResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminGetRoom(context.Context, *AdminGetRoomRequest) (*AdminGetRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoom not implemented") return nil, status.Error(codes.Unimplemented, "method AdminGetRoom not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminGetRoomRocketConfig(context.Context, *AdminGetRoomRocketConfigRequest) (*AdminGetRoomRocketConfigResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminGetRoomRocketConfig(context.Context, *AdminGetRoomRocketConfigRequest) (*AdminGetRoomRocketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminGetRoomRocketConfig not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminGetRoomSeatConfig(context.Context, *AdminGetRoomSeatConfigRequest) (*AdminGetRoomSeatConfigResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminGetRoomSeatConfig(context.Context, *AdminGetRoomSeatConfigRequest) (*AdminGetRoomSeatConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminGetRoomSeatConfig not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminGetHumanRoomRobotConfig(context.Context, *AdminGetHumanRoomRobotConfigRequest) (*AdminGetHumanRoomRobotConfigResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminGetHumanRoomRobotConfig(context.Context, *AdminGetHumanRoomRobotConfigRequest) (*AdminGetHumanRoomRobotConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGetHumanRoomRobotConfig not implemented") return nil, status.Error(codes.Unimplemented, "method AdminGetHumanRoomRobotConfig not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminListRoomPins(context.Context, *AdminListRoomPinsRequest) (*AdminListRoomPinsResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminListRoomPins(context.Context, *AdminListRoomPinsRequest) (*AdminListRoomPinsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminListRoomPins not implemented") return nil, status.Error(codes.Unimplemented, "method AdminListRoomPins not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminListRobotRooms(context.Context, *AdminListRobotRoomsRequest) (*AdminListRobotRoomsResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminListRobotRooms(context.Context, *AdminListRobotRoomsRequest) (*AdminListRobotRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminListRobotRooms not implemented") return nil, status.Error(codes.Unimplemented, "method AdminListRobotRooms not implemented")
} }
func (UnimplementedRoomQueryServiceServer) AdminFilterAvailableRoomRobots(context.Context, *AdminFilterAvailableRoomRobotsRequest) (*AdminFilterAvailableRoomRobotsResponse, error) { func (UnimplementedRoomQueryServiceServer) AdminFilterAvailableRoomRobots(context.Context, *AdminFilterAvailableRoomRobotsRequest) (*AdminFilterAvailableRoomRobotsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminFilterAvailableRoomRobots not implemented") return nil, status.Error(codes.Unimplemented, "method AdminFilterAvailableRoomRobots not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRooms(context.Context, *ListRoomsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRooms not implemented") return nil, status.Error(codes.Unimplemented, "method ListRooms not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomsByOwners(context.Context, *ListRoomsByOwnersRequest) (*ListRoomsResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomsByOwners(context.Context, *ListRoomsByOwnersRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomsByOwners not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomsByOwners not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomFeeds(context.Context, *ListRoomFeedsRequest) (*ListRoomsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomFeeds not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomFeeds not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomGiftLeaderboard(context.Context, *ListRoomGiftLeaderboardRequest) (*ListRoomGiftLeaderboardResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomGiftLeaderboard(context.Context, *ListRoomGiftLeaderboardRequest) (*ListRoomGiftLeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomGiftLeaderboard not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomContributionRank(context.Context, *ListRoomContributionRankRequest) (*ListRoomContributionRankResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomContributionRank(context.Context, *ListRoomContributionRankRequest) (*ListRoomContributionRankResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomContributionRank not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomContributionRank not implemented")
} }
func (UnimplementedRoomQueryServiceServer) GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error) { func (UnimplementedRoomQueryServiceServer) GetMyRoom(context.Context, *GetMyRoomRequest) (*GetMyRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyRoom not implemented") return nil, status.Error(codes.Unimplemented, "method GetMyRoom not implemented")
} }
func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error) { func (UnimplementedRoomQueryServiceServer) GetCurrentRoom(context.Context, *GetCurrentRoomRequest) (*GetCurrentRoomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCurrentRoom not implemented") return nil, status.Error(codes.Unimplemented, "method GetCurrentRoom not implemented")
} }
func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) { func (UnimplementedRoomQueryServiceServer) GetRoomSnapshot(context.Context, *GetRoomSnapshotRequest) (*GetRoomSnapshotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomSnapshot not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomSnapshot not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomBackgrounds(context.Context, *ListRoomBackgroundsRequest) (*ListRoomBackgroundsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBackgrounds not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomBackgrounds not implemented")
} }
func (UnimplementedRoomQueryServiceServer) GetRoomRocket(context.Context, *GetRoomRocketRequest) (*GetRoomRocketResponse, error) { func (UnimplementedRoomQueryServiceServer) GetRoomRocket(context.Context, *GetRoomRocketRequest) (*GetRoomRocketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoomRocket not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoomRocket not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomOnlineUsers(context.Context, *ListRoomOnlineUsersRequest) (*ListRoomOnlineUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomOnlineUsers not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomOnlineUsers not implemented")
} }
func (UnimplementedRoomQueryServiceServer) ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error) { func (UnimplementedRoomQueryServiceServer) ListRoomBannedUsers(context.Context, *ListRoomBannedUsersRequest) (*ListRoomBannedUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoomBannedUsers not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoomBannedUsers not implemented")
} }
func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {} func (UnimplementedRoomQueryServiceServer) mustEmbedUnimplementedRoomQueryServiceServer() {}
func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {} func (UnimplementedRoomQueryServiceServer) testEmbeddedByValue() {}
@ -2002,7 +2002,7 @@ type UnsafeRoomQueryServiceServer interface {
} }
func RegisterRoomQueryServiceServer(s grpc.ServiceRegistrar, srv RoomQueryServiceServer) { func RegisterRoomQueryServiceServer(s grpc.ServiceRegistrar, srv RoomQueryServiceServer) {
// If the following call pancis, it indicates UnimplementedRoomQueryServiceServer was // If the following call panics, it indicates UnimplementedRoomQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/user/v1/auth.proto // source: proto/user/v1/auth.proto
package userv1 package userv1
@ -236,46 +236,46 @@ type AuthServiceServer interface {
type UnimplementedAuthServiceServer struct{} type UnimplementedAuthServiceServer struct{}
func (UnimplementedAuthServiceServer) LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error) { func (UnimplementedAuthServiceServer) LoginPassword(context.Context, *LoginPasswordRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginPassword not implemented") return nil, status.Error(codes.Unimplemented, "method LoginPassword not implemented")
} }
func (UnimplementedAuthServiceServer) LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error) { func (UnimplementedAuthServiceServer) LoginThirdParty(context.Context, *LoginThirdPartyRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginThirdParty not implemented") return nil, status.Error(codes.Unimplemented, "method LoginThirdParty not implemented")
} }
func (UnimplementedAuthServiceServer) RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error) { func (UnimplementedAuthServiceServer) RegisterThirdParty(context.Context, *RegisterThirdPartyRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterThirdParty not implemented") return nil, status.Error(codes.Unimplemented, "method RegisterThirdParty not implemented")
} }
func (UnimplementedAuthServiceServer) CheckThirdPartyRegistered(context.Context, *CheckThirdPartyRegisteredRequest) (*CheckThirdPartyRegisteredResponse, error) { func (UnimplementedAuthServiceServer) CheckThirdPartyRegistered(context.Context, *CheckThirdPartyRegisteredRequest) (*CheckThirdPartyRegisteredResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckThirdPartyRegistered not implemented") return nil, status.Error(codes.Unimplemented, "method CheckThirdPartyRegistered not implemented")
} }
func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) { func (UnimplementedAuthServiceServer) SetPassword(context.Context, *SetPasswordRequest) (*SetPasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetPassword not implemented") return nil, status.Error(codes.Unimplemented, "method SetPassword not implemented")
} }
func (UnimplementedAuthServiceServer) QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error) { func (UnimplementedAuthServiceServer) QuickCreateAccount(context.Context, *QuickCreateAccountRequest) (*QuickCreateAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QuickCreateAccount not implemented") return nil, status.Error(codes.Unimplemented, "method QuickCreateAccount not implemented")
} }
func (UnimplementedAuthServiceServer) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) { func (UnimplementedAuthServiceServer) RefreshToken(context.Context, *RefreshTokenRequest) (*RefreshTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshToken not implemented") return nil, status.Error(codes.Unimplemented, "method RefreshToken not implemented")
} }
func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) { func (UnimplementedAuthServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") return nil, status.Error(codes.Unimplemented, "method Logout not implemented")
} }
func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) { func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordLoginBlocked not implemented") return nil, status.Error(codes.Unimplemented, "method RecordLoginBlocked not implemented")
} }
func (UnimplementedAuthServiceServer) CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error) { func (UnimplementedAuthServiceServer) CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckLoginRiskIPWhitelist not implemented") return nil, status.Error(codes.Unimplemented, "method CheckLoginRiskIPWhitelist not implemented")
} }
func (UnimplementedAuthServiceServer) RefreshLoginRiskWhitelistCache(context.Context, *RefreshLoginRiskWhitelistCacheRequest) (*RefreshLoginRiskWhitelistCacheResponse, error) { func (UnimplementedAuthServiceServer) RefreshLoginRiskWhitelistCache(context.Context, *RefreshLoginRiskWhitelistCacheRequest) (*RefreshLoginRiskWhitelistCacheResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshLoginRiskWhitelistCache not implemented") return nil, status.Error(codes.Unimplemented, "method RefreshLoginRiskWhitelistCache not implemented")
} }
func (UnimplementedAuthServiceServer) GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error) { func (UnimplementedAuthServiceServer) GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRegisterRiskConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetRegisterRiskConfig not implemented")
} }
func (UnimplementedAuthServiceServer) UpdateRegisterRiskConfig(context.Context, *UpdateRegisterRiskConfigRequest) (*UpdateRegisterRiskConfigResponse, error) { func (UnimplementedAuthServiceServer) UpdateRegisterRiskConfig(context.Context, *UpdateRegisterRiskConfigRequest) (*UpdateRegisterRiskConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegisterRiskConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRegisterRiskConfig not implemented")
} }
func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) { func (UnimplementedAuthServiceServer) AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented") return nil, status.Error(codes.Unimplemented, "method AppHeartbeat not implemented")
} }
func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {} func (UnimplementedAuthServiceServer) mustEmbedUnimplementedAuthServiceServer() {}
func (UnimplementedAuthServiceServer) testEmbeddedByValue() {} func (UnimplementedAuthServiceServer) testEmbeddedByValue() {}
@ -288,7 +288,7 @@ type UnsafeAuthServiceServer interface {
} }
func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) { func RegisterAuthServiceServer(s grpc.ServiceRegistrar, srv AuthServiceServer) {
// If the following call pancis, it indicates UnimplementedAuthServiceServer was // If the following call panics, it indicates UnimplementedAuthServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/user/v1/host.proto // source: proto/user/v1/host.proto
package userv1 package userv1
@ -472,100 +472,100 @@ type UserHostServiceServer interface {
type UnimplementedUserHostServiceServer struct{} type UnimplementedUserHostServiceServer struct{}
func (UnimplementedUserHostServiceServer) SearchAgencies(context.Context, *SearchAgenciesRequest) (*SearchAgenciesResponse, error) { func (UnimplementedUserHostServiceServer) SearchAgencies(context.Context, *SearchAgenciesRequest) (*SearchAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SearchAgencies not implemented") return nil, status.Error(codes.Unimplemented, "method SearchAgencies not implemented")
} }
func (UnimplementedUserHostServiceServer) ApplyToAgency(context.Context, *ApplyToAgencyRequest) (*ApplyToAgencyResponse, error) { func (UnimplementedUserHostServiceServer) ApplyToAgency(context.Context, *ApplyToAgencyRequest) (*ApplyToAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyToAgency not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyToAgency not implemented")
} }
func (UnimplementedUserHostServiceServer) ReviewAgencyApplication(context.Context, *ReviewAgencyApplicationRequest) (*ReviewAgencyApplicationResponse, error) { func (UnimplementedUserHostServiceServer) ReviewAgencyApplication(context.Context, *ReviewAgencyApplicationRequest) (*ReviewAgencyApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReviewAgencyApplication not implemented") return nil, status.Error(codes.Unimplemented, "method ReviewAgencyApplication not implemented")
} }
func (UnimplementedUserHostServiceServer) KickAgencyHost(context.Context, *KickAgencyHostRequest) (*KickAgencyHostResponse, error) { func (UnimplementedUserHostServiceServer) KickAgencyHost(context.Context, *KickAgencyHostRequest) (*KickAgencyHostResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method KickAgencyHost not implemented") return nil, status.Error(codes.Unimplemented, "method KickAgencyHost not implemented")
} }
func (UnimplementedUserHostServiceServer) InviteAgency(context.Context, *InviteAgencyRequest) (*InviteAgencyResponse, error) { func (UnimplementedUserHostServiceServer) InviteAgency(context.Context, *InviteAgencyRequest) (*InviteAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InviteAgency not implemented") return nil, status.Error(codes.Unimplemented, "method InviteAgency not implemented")
} }
func (UnimplementedUserHostServiceServer) InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error) { func (UnimplementedUserHostServiceServer) InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InviteBD not implemented") return nil, status.Error(codes.Unimplemented, "method InviteBD not implemented")
} }
func (UnimplementedUserHostServiceServer) InviteHost(context.Context, *InviteHostRequest) (*InviteHostResponse, error) { func (UnimplementedUserHostServiceServer) InviteHost(context.Context, *InviteHostRequest) (*InviteHostResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InviteHost not implemented") return nil, status.Error(codes.Unimplemented, "method InviteHost not implemented")
} }
func (UnimplementedUserHostServiceServer) ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error) { func (UnimplementedUserHostServiceServer) ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderBDs not implemented") return nil, status.Error(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
} }
func (UnimplementedUserHostServiceServer) ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error) { func (UnimplementedUserHostServiceServer) ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDLeaderAgencies not implemented") return nil, status.Error(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
} }
func (UnimplementedUserHostServiceServer) ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error) { func (UnimplementedUserHostServiceServer) ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBDAgencies not implemented") return nil, status.Error(codes.Unimplemented, "method ListBDAgencies not implemented")
} }
func (UnimplementedUserHostServiceServer) ListManagerTeamAgencies(context.Context, *ListManagerTeamAgenciesRequest) (*ListManagerTeamAgenciesResponse, error) { func (UnimplementedUserHostServiceServer) ListManagerTeamAgencies(context.Context, *ListManagerTeamAgenciesRequest) (*ListManagerTeamAgenciesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListManagerTeamAgencies not implemented") return nil, status.Error(codes.Unimplemented, "method ListManagerTeamAgencies not implemented")
} }
func (UnimplementedUserHostServiceServer) ListRoleInvitations(context.Context, *ListRoleInvitationsRequest) (*ListRoleInvitationsResponse, error) { func (UnimplementedUserHostServiceServer) ListRoleInvitations(context.Context, *ListRoleInvitationsRequest) (*ListRoleInvitationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRoleInvitations not implemented") return nil, status.Error(codes.Unimplemented, "method ListRoleInvitations not implemented")
} }
func (UnimplementedUserHostServiceServer) GetRoleInvitation(context.Context, *GetRoleInvitationRequest) (*GetRoleInvitationResponse, error) { func (UnimplementedUserHostServiceServer) GetRoleInvitation(context.Context, *GetRoleInvitationRequest) (*GetRoleInvitationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoleInvitation not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoleInvitation not implemented")
} }
func (UnimplementedUserHostServiceServer) ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error) { func (UnimplementedUserHostServiceServer) ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessRoleInvitation not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
} }
func (UnimplementedUserHostServiceServer) GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error) { func (UnimplementedUserHostServiceServer) GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostProfile not implemented") return nil, status.Error(codes.Unimplemented, "method GetHostProfile not implemented")
} }
func (UnimplementedUserHostServiceServer) BatchGetHostProfiles(context.Context, *BatchGetHostProfilesRequest) (*BatchGetHostProfilesResponse, error) { func (UnimplementedUserHostServiceServer) BatchGetHostProfiles(context.Context, *BatchGetHostProfilesRequest) (*BatchGetHostProfilesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetHostProfiles not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetHostProfiles not implemented")
} }
func (UnimplementedUserHostServiceServer) GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error) { func (UnimplementedUserHostServiceServer) GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBDProfile not implemented") return nil, status.Error(codes.Unimplemented, "method GetBDProfile not implemented")
} }
func (UnimplementedUserHostServiceServer) GetCoinSellerProfile(context.Context, *GetCoinSellerProfileRequest) (*GetCoinSellerProfileResponse, error) { func (UnimplementedUserHostServiceServer) GetCoinSellerProfile(context.Context, *GetCoinSellerProfileRequest) (*GetCoinSellerProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetCoinSellerProfile not implemented") return nil, status.Error(codes.Unimplemented, "method GetCoinSellerProfile not implemented")
} }
func (UnimplementedUserHostServiceServer) ListActiveCoinSellersInMyRegion(context.Context, *ListActiveCoinSellersInMyRegionRequest) (*ListActiveCoinSellersInMyRegionResponse, error) { func (UnimplementedUserHostServiceServer) ListActiveCoinSellersInMyRegion(context.Context, *ListActiveCoinSellersInMyRegionRequest) (*ListActiveCoinSellersInMyRegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented") return nil, status.Error(codes.Unimplemented, "method ListActiveCoinSellersInMyRegion not implemented")
} }
func (UnimplementedUserHostServiceServer) CreateSubCoinSeller(context.Context, *CreateSubCoinSellerRequest) (*CreateSubCoinSellerResponse, error) { func (UnimplementedUserHostServiceServer) CreateSubCoinSeller(context.Context, *CreateSubCoinSellerRequest) (*CreateSubCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateSubCoinSeller not implemented") return nil, status.Error(codes.Unimplemented, "method CreateSubCoinSeller not implemented")
} }
func (UnimplementedUserHostServiceServer) SubmitSubCoinSellerApplication(context.Context, *SubmitSubCoinSellerApplicationRequest) (*SubmitSubCoinSellerApplicationResponse, error) { func (UnimplementedUserHostServiceServer) SubmitSubCoinSellerApplication(context.Context, *SubmitSubCoinSellerApplicationRequest) (*SubmitSubCoinSellerApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SubmitSubCoinSellerApplication not implemented") return nil, status.Error(codes.Unimplemented, "method SubmitSubCoinSellerApplication not implemented")
} }
func (UnimplementedUserHostServiceServer) ListSubCoinSellers(context.Context, *ListSubCoinSellersRequest) (*ListSubCoinSellersResponse, error) { func (UnimplementedUserHostServiceServer) ListSubCoinSellers(context.Context, *ListSubCoinSellersRequest) (*ListSubCoinSellersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListSubCoinSellers not implemented") return nil, status.Error(codes.Unimplemented, "method ListSubCoinSellers not implemented")
} }
func (UnimplementedUserHostServiceServer) CheckCoinSellerSubRelation(context.Context, *CheckCoinSellerSubRelationRequest) (*CheckCoinSellerSubRelationResponse, error) { func (UnimplementedUserHostServiceServer) CheckCoinSellerSubRelation(context.Context, *CheckCoinSellerSubRelationRequest) (*CheckCoinSellerSubRelationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckCoinSellerSubRelation not implemented") return nil, status.Error(codes.Unimplemented, "method CheckCoinSellerSubRelation not implemented")
} }
func (UnimplementedUserHostServiceServer) GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) { func (UnimplementedUserHostServiceServer) GetUserRoleSummary(context.Context, *GetUserRoleSummaryRequest) (*GetUserRoleSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserRoleSummary not implemented") return nil, status.Error(codes.Unimplemented, "method GetUserRoleSummary not implemented")
} }
func (UnimplementedUserHostServiceServer) GetAgency(context.Context, *GetAgencyRequest) (*GetAgencyResponse, error) { func (UnimplementedUserHostServiceServer) GetAgency(context.Context, *GetAgencyRequest) (*GetAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgency not implemented") return nil, status.Error(codes.Unimplemented, "method GetAgency not implemented")
} }
func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) { func (UnimplementedUserHostServiceServer) CheckBusinessCapability(context.Context, *CheckBusinessCapabilityRequest) (*CheckBusinessCapabilityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckBusinessCapability not implemented") return nil, status.Error(codes.Unimplemented, "method CheckBusinessCapability not implemented")
} }
func (UnimplementedUserHostServiceServer) GetRoleScopePolicy(context.Context, *GetRoleScopePolicyRequest) (*GetRoleScopePolicyResponse, error) { func (UnimplementedUserHostServiceServer) GetRoleScopePolicy(context.Context, *GetRoleScopePolicyRequest) (*GetRoleScopePolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRoleScopePolicy not implemented") return nil, status.Error(codes.Unimplemented, "method GetRoleScopePolicy not implemented")
} }
func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) { func (UnimplementedUserHostServiceServer) GetAgencyMembers(context.Context, *GetAgencyMembersRequest) (*GetAgencyMembersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyMembers not implemented") return nil, status.Error(codes.Unimplemented, "method GetAgencyMembers not implemented")
} }
func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) { func (UnimplementedUserHostServiceServer) GetAgencyApplications(context.Context, *GetAgencyApplicationsRequest) (*GetAgencyApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyApplications not implemented") return nil, status.Error(codes.Unimplemented, "method GetAgencyApplications not implemented")
} }
func (UnimplementedUserHostServiceServer) UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error) { func (UnimplementedUserHostServiceServer) UpdateAgencyProfile(context.Context, *UpdateAgencyProfileRequest) (*UpdateAgencyProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateAgencyProfile not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateAgencyProfile not implemented")
} }
func (UnimplementedUserHostServiceServer) GetHostEngagementStats(context.Context, *GetHostEngagementStatsRequest) (*GetHostEngagementStatsResponse, error) { func (UnimplementedUserHostServiceServer) GetHostEngagementStats(context.Context, *GetHostEngagementStatsRequest) (*GetHostEngagementStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostEngagementStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetHostEngagementStats not implemented")
} }
func (UnimplementedUserHostServiceServer) RecordPrivateMessageEvent(context.Context, *RecordPrivateMessageEventRequest) (*RecordPrivateMessageEventResponse, error) { func (UnimplementedUserHostServiceServer) RecordPrivateMessageEvent(context.Context, *RecordPrivateMessageEventRequest) (*RecordPrivateMessageEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordPrivateMessageEvent not implemented") return nil, status.Error(codes.Unimplemented, "method RecordPrivateMessageEvent not implemented")
} }
func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {} func (UnimplementedUserHostServiceServer) mustEmbedUnimplementedUserHostServiceServer() {}
func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {} func (UnimplementedUserHostServiceServer) testEmbeddedByValue() {}
@ -578,7 +578,7 @@ type UnsafeUserHostServiceServer interface {
} }
func RegisterUserHostServiceServer(s grpc.ServiceRegistrar, srv UserHostServiceServer) { func RegisterUserHostServiceServer(s grpc.ServiceRegistrar, srv UserHostServiceServer) {
// If the following call pancis, it indicates UnimplementedUserHostServiceServer was // If the following call panics, it indicates UnimplementedUserHostServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1496,40 +1496,40 @@ type UserHostAdminServiceServer interface {
type UnimplementedUserHostAdminServiceServer struct{} type UnimplementedUserHostAdminServiceServer struct{}
func (UnimplementedUserHostAdminServiceServer) CreateBDLeader(context.Context, *CreateBDLeaderRequest) (*CreateBDLeaderResponse, error) { func (UnimplementedUserHostAdminServiceServer) CreateBDLeader(context.Context, *CreateBDLeaderRequest) (*CreateBDLeaderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateBDLeader not implemented") return nil, status.Error(codes.Unimplemented, "method CreateBDLeader not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) CreateBD(context.Context, *CreateBDRequest) (*CreateBDResponse, error) { func (UnimplementedUserHostAdminServiceServer) CreateBD(context.Context, *CreateBDRequest) (*CreateBDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateBD not implemented") return nil, status.Error(codes.Unimplemented, "method CreateBD not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) SetBDStatus(context.Context, *SetBDStatusRequest) (*SetBDStatusResponse, error) { func (UnimplementedUserHostAdminServiceServer) SetBDStatus(context.Context, *SetBDStatusRequest) (*SetBDStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetBDStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetBDStatus not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) CreateCoinSeller(context.Context, *CreateCoinSellerRequest) (*CreateCoinSellerResponse, error) { func (UnimplementedUserHostAdminServiceServer) CreateCoinSeller(context.Context, *CreateCoinSellerRequest) (*CreateCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateCoinSeller not implemented") return nil, status.Error(codes.Unimplemented, "method CreateCoinSeller not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) SetCoinSellerStatus(context.Context, *SetCoinSellerStatusRequest) (*SetCoinSellerStatusResponse, error) { func (UnimplementedUserHostAdminServiceServer) SetCoinSellerStatus(context.Context, *SetCoinSellerStatusRequest) (*SetCoinSellerStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetCoinSellerStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetCoinSellerStatus not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) ReviewSubCoinSellerApplication(context.Context, *ReviewSubCoinSellerApplicationRequest) (*ReviewSubCoinSellerApplicationResponse, error) { func (UnimplementedUserHostAdminServiceServer) ReviewSubCoinSellerApplication(context.Context, *ReviewSubCoinSellerApplicationRequest) (*ReviewSubCoinSellerApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReviewSubCoinSellerApplication not implemented") return nil, status.Error(codes.Unimplemented, "method ReviewSubCoinSellerApplication not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) CreateAgency(context.Context, *CreateAgencyRequest) (*CreateAgencyResponse, error) { func (UnimplementedUserHostAdminServiceServer) CreateAgency(context.Context, *CreateAgencyRequest) (*CreateAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateAgency not implemented") return nil, status.Error(codes.Unimplemented, "method CreateAgency not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) AdminAddAgencyHost(context.Context, *AdminAddAgencyHostRequest) (*AdminAddAgencyHostResponse, error) { func (UnimplementedUserHostAdminServiceServer) AdminAddAgencyHost(context.Context, *AdminAddAgencyHostRequest) (*AdminAddAgencyHostResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminAddAgencyHost not implemented") return nil, status.Error(codes.Unimplemented, "method AdminAddAgencyHost not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) CloseAgency(context.Context, *CloseAgencyRequest) (*CloseAgencyResponse, error) { func (UnimplementedUserHostAdminServiceServer) CloseAgency(context.Context, *CloseAgencyRequest) (*CloseAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CloseAgency not implemented") return nil, status.Error(codes.Unimplemented, "method CloseAgency not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) DeleteAgency(context.Context, *DeleteAgencyRequest) (*DeleteAgencyResponse, error) { func (UnimplementedUserHostAdminServiceServer) DeleteAgency(context.Context, *DeleteAgencyRequest) (*DeleteAgencyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteAgency not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteAgency not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) SetAgencyStatus(context.Context, *SetAgencyStatusRequest) (*SetAgencyStatusResponse, error) { func (UnimplementedUserHostAdminServiceServer) SetAgencyStatus(context.Context, *SetAgencyStatusRequest) (*SetAgencyStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAgencyStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetAgencyStatus not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) SetAgencyJoinEnabled(context.Context, *SetAgencyJoinEnabledRequest) (*SetAgencyJoinEnabledResponse, error) { func (UnimplementedUserHostAdminServiceServer) SetAgencyJoinEnabled(context.Context, *SetAgencyJoinEnabledRequest) (*SetAgencyJoinEnabledResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented") return nil, status.Error(codes.Unimplemented, "method SetAgencyJoinEnabled not implemented")
} }
func (UnimplementedUserHostAdminServiceServer) mustEmbedUnimplementedUserHostAdminServiceServer() {} func (UnimplementedUserHostAdminServiceServer) mustEmbedUnimplementedUserHostAdminServiceServer() {}
func (UnimplementedUserHostAdminServiceServer) testEmbeddedByValue() {} func (UnimplementedUserHostAdminServiceServer) testEmbeddedByValue() {}
@ -1542,7 +1542,7 @@ type UnsafeUserHostAdminServiceServer interface {
} }
func RegisterUserHostAdminServiceServer(s grpc.ServiceRegistrar, srv UserHostAdminServiceServer) { func RegisterUserHostAdminServiceServer(s grpc.ServiceRegistrar, srv UserHostAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedUserHostAdminServiceServer was // If the following call panics, it indicates UnimplementedUserHostAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/user/v1/user.proto // source: proto/user/v1/user.proto
package userv1 package userv1
@ -379,79 +379,79 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct{} type UnimplementedUserServiceServer struct{}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) { func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented") return nil, status.Error(codes.Unimplemented, "method GetUser not implemented")
} }
func (UnimplementedUserServiceServer) GetInviteAttribution(context.Context, *GetInviteAttributionRequest) (*GetInviteAttributionResponse, error) { func (UnimplementedUserServiceServer) GetInviteAttribution(context.Context, *GetInviteAttributionRequest) (*GetInviteAttributionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInviteAttribution not implemented") return nil, status.Error(codes.Unimplemented, "method GetInviteAttribution not implemented")
} }
func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) { func (UnimplementedUserServiceServer) BusinessUserLookup(context.Context, *BusinessUserLookupRequest) (*BusinessUserLookupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BusinessUserLookup not implemented") return nil, status.Error(codes.Unimplemented, "method BusinessUserLookup not implemented")
} }
func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) { func (UnimplementedUserServiceServer) GetMyProfileStats(context.Context, *GetMyProfileStatsRequest) (*GetMyProfileStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyProfileStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetMyProfileStats not implemented")
} }
func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) { func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUsers not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetUsers not implemented")
} }
func (UnimplementedUserServiceServer) BatchGetUserAdminProfiles(context.Context, *BatchGetUserAdminProfilesRequest) (*BatchGetUserAdminProfilesResponse, error) { func (UnimplementedUserServiceServer) BatchGetUserAdminProfiles(context.Context, *BatchGetUserAdminProfilesRequest) (*BatchGetUserAdminProfilesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserAdminProfiles not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetUserAdminProfiles not implemented")
} }
func (UnimplementedUserServiceServer) AdminIssueUserAccessToken(context.Context, *AdminIssueUserAccessTokenRequest) (*AdminIssueUserAccessTokenResponse, error) { func (UnimplementedUserServiceServer) AdminIssueUserAccessToken(context.Context, *AdminIssueUserAccessTokenRequest) (*AdminIssueUserAccessTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminIssueUserAccessToken not implemented") return nil, status.Error(codes.Unimplemented, "method AdminIssueUserAccessToken not implemented")
} }
func (UnimplementedUserServiceServer) BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error) { func (UnimplementedUserServiceServer) BatchGetRoomBasicUsers(context.Context, *BatchGetRoomBasicUsersRequest) (*BatchGetRoomBasicUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetRoomBasicUsers not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetRoomBasicUsers not implemented")
} }
func (UnimplementedUserServiceServer) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) { func (UnimplementedUserServiceServer) ListUserIDs(context.Context, *ListUserIDsRequest) (*ListUserIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserIDs not implemented") return nil, status.Error(codes.Unimplemented, "method ListUserIDs not implemented")
} }
func (UnimplementedUserServiceServer) GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error) { func (UnimplementedUserServiceServer) GetUserMicLifetimeStats(context.Context, *GetUserMicLifetimeStatsRequest) (*GetUserMicLifetimeStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetUserMicLifetimeStats not implemented")
} }
func (UnimplementedUserServiceServer) UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UpdateUserProfileResponse, error) { func (UnimplementedUserServiceServer) UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UpdateUserProfileResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfile not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateUserProfile not implemented")
} }
func (UnimplementedUserServiceServer) UpdateUserProfileBackground(context.Context, *UpdateUserProfileBackgroundRequest) (*UpdateUserProfileBackgroundResponse, error) { func (UnimplementedUserServiceServer) UpdateUserProfileBackground(context.Context, *UpdateUserProfileBackgroundRequest) (*UpdateUserProfileBackgroundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfileBackground not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateUserProfileBackground not implemented")
} }
func (UnimplementedUserServiceServer) UpdateUserContactInfo(context.Context, *UpdateUserContactInfoRequest) (*UpdateUserContactInfoResponse, error) { func (UnimplementedUserServiceServer) UpdateUserContactInfo(context.Context, *UpdateUserContactInfoRequest) (*UpdateUserContactInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserContactInfo not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateUserContactInfo not implemented")
} }
func (UnimplementedUserServiceServer) UpdateUserWithdrawAddress(context.Context, *UpdateUserWithdrawAddressRequest) (*UpdateUserWithdrawAddressResponse, error) { func (UnimplementedUserServiceServer) UpdateUserWithdrawAddress(context.Context, *UpdateUserWithdrawAddressRequest) (*UpdateUserWithdrawAddressResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserWithdrawAddress not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateUserWithdrawAddress not implemented")
} }
func (UnimplementedUserServiceServer) ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) { func (UnimplementedUserServiceServer) ChangeUserCountry(context.Context, *ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeUserCountry not implemented") return nil, status.Error(codes.Unimplemented, "method ChangeUserCountry not implemented")
} }
func (UnimplementedUserServiceServer) AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error) { func (UnimplementedUserServiceServer) AdminChangeUserCountry(context.Context, *AdminChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminChangeUserCountry not implemented") return nil, status.Error(codes.Unimplemented, "method AdminChangeUserCountry not implemented")
} }
func (UnimplementedUserServiceServer) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) { func (UnimplementedUserServiceServer) SetUserStatus(context.Context, *SetUserStatusRequest) (*SetUserStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetUserStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetUserStatus not implemented")
} }
func (UnimplementedUserServiceServer) AdminBanUser(context.Context, *AdminBanUserRequest) (*AdminBanUserResponse, error) { func (UnimplementedUserServiceServer) AdminBanUser(context.Context, *AdminBanUserRequest) (*AdminBanUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminBanUser not implemented") return nil, status.Error(codes.Unimplemented, "method AdminBanUser not implemented")
} }
func (UnimplementedUserServiceServer) AdminUnbanUser(context.Context, *AdminUnbanUserRequest) (*AdminUnbanUserResponse, error) { func (UnimplementedUserServiceServer) AdminUnbanUser(context.Context, *AdminUnbanUserRequest) (*AdminUnbanUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminUnbanUser not implemented") return nil, status.Error(codes.Unimplemented, "method AdminUnbanUser not implemented")
} }
func (UnimplementedUserServiceServer) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) { func (UnimplementedUserServiceServer) CreateManagerUserBlock(context.Context, *CreateManagerUserBlockRequest) (*CreateManagerUserBlockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateManagerUserBlock not implemented") return nil, status.Error(codes.Unimplemented, "method CreateManagerUserBlock not implemented")
} }
func (UnimplementedUserServiceServer) ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error) { func (UnimplementedUserServiceServer) ListManagerUserBlocks(context.Context, *ListManagerUserBlocksRequest) (*ListManagerUserBlocksResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListManagerUserBlocks not implemented") return nil, status.Error(codes.Unimplemented, "method ListManagerUserBlocks not implemented")
} }
func (UnimplementedUserServiceServer) UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error) { func (UnimplementedUserServiceServer) UnblockManagerUser(context.Context, *UnblockManagerUserRequest) (*UnblockManagerUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnblockManagerUser not implemented") return nil, status.Error(codes.Unimplemented, "method UnblockManagerUser not implemented")
} }
func (UnimplementedUserServiceServer) CompleteOnboarding(context.Context, *CompleteOnboardingRequest) (*CompleteOnboardingResponse, error) { func (UnimplementedUserServiceServer) CompleteOnboarding(context.Context, *CompleteOnboardingRequest) (*CompleteOnboardingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompleteOnboarding not implemented") return nil, status.Error(codes.Unimplemented, "method CompleteOnboarding not implemented")
} }
func (UnimplementedUserServiceServer) SearchInviteReferrer(context.Context, *SearchInviteReferrerRequest) (*SearchInviteReferrerResponse, error) { func (UnimplementedUserServiceServer) SearchInviteReferrer(context.Context, *SearchInviteReferrerRequest) (*SearchInviteReferrerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SearchInviteReferrer not implemented") return nil, status.Error(codes.Unimplemented, "method SearchInviteReferrer not implemented")
} }
func (UnimplementedUserServiceServer) BindInviteReferrer(context.Context, *BindInviteReferrerRequest) (*BindInviteReferrerResponse, error) { func (UnimplementedUserServiceServer) BindInviteReferrer(context.Context, *BindInviteReferrerRequest) (*BindInviteReferrerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BindInviteReferrer not implemented") return nil, status.Error(codes.Unimplemented, "method BindInviteReferrer not implemented")
} }
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {} func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
func (UnimplementedUserServiceServer) testEmbeddedByValue() {} func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
@ -464,7 +464,7 @@ type UnsafeUserServiceServer interface {
} }
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) { func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
// If the following call pancis, it indicates UnimplementedUserServiceServer was // If the following call panics, it indicates UnimplementedUserServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1215,37 +1215,37 @@ type UserSocialServiceServer interface {
type UnimplementedUserSocialServiceServer struct{} type UnimplementedUserSocialServiceServer struct{}
func (UnimplementedUserSocialServiceServer) RecordProfileVisit(context.Context, *RecordProfileVisitRequest) (*RecordProfileVisitResponse, error) { func (UnimplementedUserSocialServiceServer) RecordProfileVisit(context.Context, *RecordProfileVisitRequest) (*RecordProfileVisitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecordProfileVisit not implemented") return nil, status.Error(codes.Unimplemented, "method RecordProfileVisit not implemented")
} }
func (UnimplementedUserSocialServiceServer) ListProfileVisitors(context.Context, *ListProfileVisitorsRequest) (*ListProfileVisitorsResponse, error) { func (UnimplementedUserSocialServiceServer) ListProfileVisitors(context.Context, *ListProfileVisitorsRequest) (*ListProfileVisitorsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProfileVisitors not implemented") return nil, status.Error(codes.Unimplemented, "method ListProfileVisitors not implemented")
} }
func (UnimplementedUserSocialServiceServer) FollowUser(context.Context, *FollowUserRequest) (*FollowUserResponse, error) { func (UnimplementedUserSocialServiceServer) FollowUser(context.Context, *FollowUserRequest) (*FollowUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FollowUser not implemented") return nil, status.Error(codes.Unimplemented, "method FollowUser not implemented")
} }
func (UnimplementedUserSocialServiceServer) UnfollowUser(context.Context, *UnfollowUserRequest) (*UnfollowUserResponse, error) { func (UnimplementedUserSocialServiceServer) UnfollowUser(context.Context, *UnfollowUserRequest) (*UnfollowUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnfollowUser not implemented") return nil, status.Error(codes.Unimplemented, "method UnfollowUser not implemented")
} }
func (UnimplementedUserSocialServiceServer) ListFollowing(context.Context, *ListFollowingRequest) (*ListFollowingResponse, error) { func (UnimplementedUserSocialServiceServer) ListFollowing(context.Context, *ListFollowingRequest) (*ListFollowingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFollowing not implemented") return nil, status.Error(codes.Unimplemented, "method ListFollowing not implemented")
} }
func (UnimplementedUserSocialServiceServer) ApplyFriend(context.Context, *ApplyFriendRequest) (*ApplyFriendResponse, error) { func (UnimplementedUserSocialServiceServer) ApplyFriend(context.Context, *ApplyFriendRequest) (*ApplyFriendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyFriend not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyFriend not implemented")
} }
func (UnimplementedUserSocialServiceServer) AcceptFriendApplication(context.Context, *AcceptFriendApplicationRequest) (*AcceptFriendApplicationResponse, error) { func (UnimplementedUserSocialServiceServer) AcceptFriendApplication(context.Context, *AcceptFriendApplicationRequest) (*AcceptFriendApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AcceptFriendApplication not implemented") return nil, status.Error(codes.Unimplemented, "method AcceptFriendApplication not implemented")
} }
func (UnimplementedUserSocialServiceServer) DeleteFriend(context.Context, *DeleteFriendRequest) (*DeleteFriendResponse, error) { func (UnimplementedUserSocialServiceServer) DeleteFriend(context.Context, *DeleteFriendRequest) (*DeleteFriendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteFriend not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteFriend not implemented")
} }
func (UnimplementedUserSocialServiceServer) ListFriends(context.Context, *ListFriendsRequest) (*ListFriendsResponse, error) { func (UnimplementedUserSocialServiceServer) ListFriends(context.Context, *ListFriendsRequest) (*ListFriendsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFriends not implemented") return nil, status.Error(codes.Unimplemented, "method ListFriends not implemented")
} }
func (UnimplementedUserSocialServiceServer) ListFriendApplications(context.Context, *ListFriendApplicationsRequest) (*ListFriendApplicationsResponse, error) { func (UnimplementedUserSocialServiceServer) ListFriendApplications(context.Context, *ListFriendApplicationsRequest) (*ListFriendApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListFriendApplications not implemented") return nil, status.Error(codes.Unimplemented, "method ListFriendApplications not implemented")
} }
func (UnimplementedUserSocialServiceServer) SubmitReport(context.Context, *SubmitReportRequest) (*SubmitReportResponse, error) { func (UnimplementedUserSocialServiceServer) SubmitReport(context.Context, *SubmitReportRequest) (*SubmitReportResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SubmitReport not implemented") return nil, status.Error(codes.Unimplemented, "method SubmitReport not implemented")
} }
func (UnimplementedUserSocialServiceServer) mustEmbedUnimplementedUserSocialServiceServer() {} func (UnimplementedUserSocialServiceServer) mustEmbedUnimplementedUserSocialServiceServer() {}
func (UnimplementedUserSocialServiceServer) testEmbeddedByValue() {} func (UnimplementedUserSocialServiceServer) testEmbeddedByValue() {}
@ -1258,7 +1258,7 @@ type UnsafeUserSocialServiceServer interface {
} }
func RegisterUserSocialServiceServer(s grpc.ServiceRegistrar, srv UserSocialServiceServer) { func RegisterUserSocialServiceServer(s grpc.ServiceRegistrar, srv UserSocialServiceServer) {
// If the following call pancis, it indicates UnimplementedUserSocialServiceServer was // If the following call panics, it indicates UnimplementedUserSocialServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1662,28 +1662,28 @@ type UserCPServiceServer interface {
type UnimplementedUserCPServiceServer struct{} type UnimplementedUserCPServiceServer struct{}
func (UnimplementedUserCPServiceServer) ListCPApplications(context.Context, *ListCPApplicationsRequest) (*ListCPApplicationsResponse, error) { func (UnimplementedUserCPServiceServer) ListCPApplications(context.Context, *ListCPApplicationsRequest) (*ListCPApplicationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPApplications not implemented") return nil, status.Error(codes.Unimplemented, "method ListCPApplications not implemented")
} }
func (UnimplementedUserCPServiceServer) AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error) { func (UnimplementedUserCPServiceServer) AcceptCPApplication(context.Context, *AcceptCPApplicationRequest) (*AcceptCPApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AcceptCPApplication not implemented") return nil, status.Error(codes.Unimplemented, "method AcceptCPApplication not implemented")
} }
func (UnimplementedUserCPServiceServer) RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error) { func (UnimplementedUserCPServiceServer) RejectCPApplication(context.Context, *RejectCPApplicationRequest) (*RejectCPApplicationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RejectCPApplication not implemented") return nil, status.Error(codes.Unimplemented, "method RejectCPApplication not implemented")
} }
func (UnimplementedUserCPServiceServer) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) { func (UnimplementedUserCPServiceServer) ListCPRelationships(context.Context, *ListCPRelationshipsRequest) (*ListCPRelationshipsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPRelationships not implemented") return nil, status.Error(codes.Unimplemented, "method ListCPRelationships not implemented")
} }
func (UnimplementedUserCPServiceServer) ListCPIntimacyLeaderboard(context.Context, *ListCPIntimacyLeaderboardRequest) (*ListCPIntimacyLeaderboardResponse, error) { func (UnimplementedUserCPServiceServer) ListCPIntimacyLeaderboard(context.Context, *ListCPIntimacyLeaderboardRequest) (*ListCPIntimacyLeaderboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPIntimacyLeaderboard not implemented") return nil, status.Error(codes.Unimplemented, "method ListCPIntimacyLeaderboard not implemented")
} }
func (UnimplementedUserCPServiceServer) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) { func (UnimplementedUserCPServiceServer) PrepareBreakCPRelationship(context.Context, *PrepareBreakCPRelationshipRequest) (*PrepareBreakCPRelationshipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented") return nil, status.Error(codes.Unimplemented, "method PrepareBreakCPRelationship not implemented")
} }
func (UnimplementedUserCPServiceServer) ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error) { func (UnimplementedUserCPServiceServer) ConfirmBreakCPRelationship(context.Context, *ConfirmBreakCPRelationshipRequest) (*ConfirmBreakCPRelationshipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmBreakCPRelationship not implemented") return nil, status.Error(codes.Unimplemented, "method ConfirmBreakCPRelationship not implemented")
} }
func (UnimplementedUserCPServiceServer) CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error) { func (UnimplementedUserCPServiceServer) CancelBreakCPRelationship(context.Context, *CancelBreakCPRelationshipRequest) (*CancelBreakCPRelationshipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CancelBreakCPRelationship not implemented") return nil, status.Error(codes.Unimplemented, "method CancelBreakCPRelationship not implemented")
} }
func (UnimplementedUserCPServiceServer) mustEmbedUnimplementedUserCPServiceServer() {} func (UnimplementedUserCPServiceServer) mustEmbedUnimplementedUserCPServiceServer() {}
func (UnimplementedUserCPServiceServer) testEmbeddedByValue() {} func (UnimplementedUserCPServiceServer) testEmbeddedByValue() {}
@ -1696,7 +1696,7 @@ type UnsafeUserCPServiceServer interface {
} }
func RegisterUserCPServiceServer(s grpc.ServiceRegistrar, srv UserCPServiceServer) { func RegisterUserCPServiceServer(s grpc.ServiceRegistrar, srv UserCPServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCPServiceServer was // If the following call panics, it indicates UnimplementedUserCPServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -1956,10 +1956,10 @@ type UserCPInternalServiceServer interface {
type UnimplementedUserCPInternalServiceServer struct{} type UnimplementedUserCPInternalServiceServer struct{}
func (UnimplementedUserCPInternalServiceServer) ConsumeRoomGiftCPEvent(context.Context, *ConsumeRoomGiftCPEventRequest) (*ConsumeRoomGiftCPEventResponse, error) { func (UnimplementedUserCPInternalServiceServer) ConsumeRoomGiftCPEvent(context.Context, *ConsumeRoomGiftCPEventRequest) (*ConsumeRoomGiftCPEventResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented") return nil, status.Error(codes.Unimplemented, "method ConsumeRoomGiftCPEvent not implemented")
} }
func (UnimplementedUserCPInternalServiceServer) ListCPWeeklyRankEntries(context.Context, *ListCPWeeklyRankEntriesRequest) (*ListCPWeeklyRankEntriesResponse, error) { func (UnimplementedUserCPInternalServiceServer) ListCPWeeklyRankEntries(context.Context, *ListCPWeeklyRankEntriesRequest) (*ListCPWeeklyRankEntriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCPWeeklyRankEntries not implemented") return nil, status.Error(codes.Unimplemented, "method ListCPWeeklyRankEntries not implemented")
} }
func (UnimplementedUserCPInternalServiceServer) mustEmbedUnimplementedUserCPInternalServiceServer() {} func (UnimplementedUserCPInternalServiceServer) mustEmbedUnimplementedUserCPInternalServiceServer() {}
func (UnimplementedUserCPInternalServiceServer) testEmbeddedByValue() {} func (UnimplementedUserCPInternalServiceServer) testEmbeddedByValue() {}
@ -1972,7 +1972,7 @@ type UnsafeUserCPInternalServiceServer interface {
} }
func RegisterUserCPInternalServiceServer(s grpc.ServiceRegistrar, srv UserCPInternalServiceServer) { func RegisterUserCPInternalServiceServer(s grpc.ServiceRegistrar, srv UserCPInternalServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCPInternalServiceServer was // If the following call panics, it indicates UnimplementedUserCPInternalServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2165,25 +2165,25 @@ type UserCronServiceServer interface {
type UnimplementedUserCronServiceServer struct{} type UnimplementedUserCronServiceServer struct{}
func (UnimplementedUserCronServiceServer) ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) ProcessLoginIPRiskBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessLoginIPRiskBatch not implemented")
} }
func (UnimplementedUserCronServiceServer) ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) ProcessRegionRebuildBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessRegionRebuildBatch not implemented")
} }
func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) CompensateMicOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompensateMicOpenSessions not implemented") return nil, status.Error(codes.Unimplemented, "method CompensateMicOpenSessions not implemented")
} }
func (UnimplementedUserCronServiceServer) CompensateRoomOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) CompensateRoomOpenSessions(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompensateRoomOpenSessions not implemented") return nil, status.Error(codes.Unimplemented, "method CompensateRoomOpenSessions not implemented")
} }
func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) ExpireManagerUserBlocks(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented") return nil, status.Error(codes.Unimplemented, "method ExpireManagerUserBlocks not implemented")
} }
func (UnimplementedUserCronServiceServer) ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) ExpireAdminUserBans(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpireAdminUserBans not implemented") return nil, status.Error(codes.Unimplemented, "method ExpireAdminUserBans not implemented")
} }
func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedUserCronServiceServer) RefreshCPIntimacyLeaderboard(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented") return nil, status.Error(codes.Unimplemented, "method RefreshCPIntimacyLeaderboard not implemented")
} }
func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {} func (UnimplementedUserCronServiceServer) mustEmbedUnimplementedUserCronServiceServer() {}
func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {} func (UnimplementedUserCronServiceServer) testEmbeddedByValue() {}
@ -2196,7 +2196,7 @@ type UnsafeUserCronServiceServer interface {
} }
func RegisterUserCronServiceServer(s grpc.ServiceRegistrar, srv UserCronServiceServer) { func RegisterUserCronServiceServer(s grpc.ServiceRegistrar, srv UserCronServiceServer) {
// If the following call pancis, it indicates UnimplementedUserCronServiceServer was // If the following call panics, it indicates UnimplementedUserCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2434,10 +2434,10 @@ type UserDeviceServiceServer interface {
type UnimplementedUserDeviceServiceServer struct{} type UnimplementedUserDeviceServiceServer struct{}
func (UnimplementedUserDeviceServiceServer) BindPushToken(context.Context, *BindPushTokenRequest) (*BindPushTokenResponse, error) { func (UnimplementedUserDeviceServiceServer) BindPushToken(context.Context, *BindPushTokenRequest) (*BindPushTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BindPushToken not implemented") return nil, status.Error(codes.Unimplemented, "method BindPushToken not implemented")
} }
func (UnimplementedUserDeviceServiceServer) DeletePushToken(context.Context, *DeletePushTokenRequest) (*DeletePushTokenResponse, error) { func (UnimplementedUserDeviceServiceServer) DeletePushToken(context.Context, *DeletePushTokenRequest) (*DeletePushTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeletePushToken not implemented") return nil, status.Error(codes.Unimplemented, "method DeletePushToken not implemented")
} }
func (UnimplementedUserDeviceServiceServer) mustEmbedUnimplementedUserDeviceServiceServer() {} func (UnimplementedUserDeviceServiceServer) mustEmbedUnimplementedUserDeviceServiceServer() {}
func (UnimplementedUserDeviceServiceServer) testEmbeddedByValue() {} func (UnimplementedUserDeviceServiceServer) testEmbeddedByValue() {}
@ -2450,7 +2450,7 @@ type UnsafeUserDeviceServiceServer interface {
} }
func RegisterUserDeviceServiceServer(s grpc.ServiceRegistrar, srv UserDeviceServiceServer) { func RegisterUserDeviceServiceServer(s grpc.ServiceRegistrar, srv UserDeviceServiceServer) {
// If the following call pancis, it indicates UnimplementedUserDeviceServiceServer was // If the following call panics, it indicates UnimplementedUserDeviceServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2565,7 +2565,7 @@ type AppRegistryServiceServer interface {
type UnimplementedAppRegistryServiceServer struct{} type UnimplementedAppRegistryServiceServer struct{}
func (UnimplementedAppRegistryServiceServer) ResolveApp(context.Context, *ResolveAppRequest) (*ResolveAppResponse, error) { func (UnimplementedAppRegistryServiceServer) ResolveApp(context.Context, *ResolveAppRequest) (*ResolveAppResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResolveApp not implemented") return nil, status.Error(codes.Unimplemented, "method ResolveApp not implemented")
} }
func (UnimplementedAppRegistryServiceServer) mustEmbedUnimplementedAppRegistryServiceServer() {} func (UnimplementedAppRegistryServiceServer) mustEmbedUnimplementedAppRegistryServiceServer() {}
func (UnimplementedAppRegistryServiceServer) testEmbeddedByValue() {} func (UnimplementedAppRegistryServiceServer) testEmbeddedByValue() {}
@ -2578,7 +2578,7 @@ type UnsafeAppRegistryServiceServer interface {
} }
func RegisterAppRegistryServiceServer(s grpc.ServiceRegistrar, srv AppRegistryServiceServer) { func RegisterAppRegistryServiceServer(s grpc.ServiceRegistrar, srv AppRegistryServiceServer) {
// If the following call pancis, it indicates UnimplementedAppRegistryServiceServer was // If the following call panics, it indicates UnimplementedAppRegistryServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2686,10 +2686,10 @@ type CountryAdminServiceServer interface {
type UnimplementedCountryAdminServiceServer struct{} type UnimplementedCountryAdminServiceServer struct{}
func (UnimplementedCountryAdminServiceServer) ListCountries(context.Context, *ListCountriesRequest) (*ListCountriesResponse, error) { func (UnimplementedCountryAdminServiceServer) ListCountries(context.Context, *ListCountriesRequest) (*ListCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCountries not implemented") return nil, status.Error(codes.Unimplemented, "method ListCountries not implemented")
} }
func (UnimplementedCountryAdminServiceServer) UpdateCountry(context.Context, *UpdateCountryRequest) (*CountryResponse, error) { func (UnimplementedCountryAdminServiceServer) UpdateCountry(context.Context, *UpdateCountryRequest) (*CountryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateCountry not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateCountry not implemented")
} }
func (UnimplementedCountryAdminServiceServer) mustEmbedUnimplementedCountryAdminServiceServer() {} func (UnimplementedCountryAdminServiceServer) mustEmbedUnimplementedCountryAdminServiceServer() {}
func (UnimplementedCountryAdminServiceServer) testEmbeddedByValue() {} func (UnimplementedCountryAdminServiceServer) testEmbeddedByValue() {}
@ -2702,7 +2702,7 @@ type UnsafeCountryAdminServiceServer interface {
} }
func RegisterCountryAdminServiceServer(s grpc.ServiceRegistrar, srv CountryAdminServiceServer) { func RegisterCountryAdminServiceServer(s grpc.ServiceRegistrar, srv CountryAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedCountryAdminServiceServer was // If the following call panics, it indicates UnimplementedCountryAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2830,10 +2830,10 @@ type CountryQueryServiceServer interface {
type UnimplementedCountryQueryServiceServer struct{} type UnimplementedCountryQueryServiceServer struct{}
func (UnimplementedCountryQueryServiceServer) ListRegistrationCountries(context.Context, *ListRegistrationCountriesRequest) (*ListRegistrationCountriesResponse, error) { func (UnimplementedCountryQueryServiceServer) ListRegistrationCountries(context.Context, *ListRegistrationCountriesRequest) (*ListRegistrationCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRegistrationCountries not implemented") return nil, status.Error(codes.Unimplemented, "method ListRegistrationCountries not implemented")
} }
func (UnimplementedCountryQueryServiceServer) ListLoginRiskBlockedCountries(context.Context, *ListLoginRiskBlockedCountriesRequest) (*ListLoginRiskBlockedCountriesResponse, error) { func (UnimplementedCountryQueryServiceServer) ListLoginRiskBlockedCountries(context.Context, *ListLoginRiskBlockedCountriesRequest) (*ListLoginRiskBlockedCountriesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented") return nil, status.Error(codes.Unimplemented, "method ListLoginRiskBlockedCountries not implemented")
} }
func (UnimplementedCountryQueryServiceServer) mustEmbedUnimplementedCountryQueryServiceServer() {} func (UnimplementedCountryQueryServiceServer) mustEmbedUnimplementedCountryQueryServiceServer() {}
func (UnimplementedCountryQueryServiceServer) testEmbeddedByValue() {} func (UnimplementedCountryQueryServiceServer) testEmbeddedByValue() {}
@ -2846,7 +2846,7 @@ type UnsafeCountryQueryServiceServer interface {
} }
func RegisterCountryQueryServiceServer(s grpc.ServiceRegistrar, srv CountryQueryServiceServer) { func RegisterCountryQueryServiceServer(s grpc.ServiceRegistrar, srv CountryQueryServiceServer) {
// If the following call pancis, it indicates UnimplementedCountryQueryServiceServer was // If the following call panics, it indicates UnimplementedCountryQueryServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -3002,16 +3002,16 @@ type RegionAdminServiceServer interface {
type UnimplementedRegionAdminServiceServer struct{} type UnimplementedRegionAdminServiceServer struct{}
func (UnimplementedRegionAdminServiceServer) ListRegions(context.Context, *ListRegionsRequest) (*ListRegionsResponse, error) { func (UnimplementedRegionAdminServiceServer) ListRegions(context.Context, *ListRegionsRequest) (*ListRegionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRegions not implemented") return nil, status.Error(codes.Unimplemented, "method ListRegions not implemented")
} }
func (UnimplementedRegionAdminServiceServer) GetRegion(context.Context, *GetRegionRequest) (*RegionResponse, error) { func (UnimplementedRegionAdminServiceServer) GetRegion(context.Context, *GetRegionRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRegion not implemented") return nil, status.Error(codes.Unimplemented, "method GetRegion not implemented")
} }
func (UnimplementedRegionAdminServiceServer) UpdateRegion(context.Context, *UpdateRegionRequest) (*RegionResponse, error) { func (UnimplementedRegionAdminServiceServer) UpdateRegion(context.Context, *UpdateRegionRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegion not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRegion not implemented")
} }
func (UnimplementedRegionAdminServiceServer) ReplaceRegionCountries(context.Context, *ReplaceRegionCountriesRequest) (*RegionResponse, error) { func (UnimplementedRegionAdminServiceServer) ReplaceRegionCountries(context.Context, *ReplaceRegionCountriesRequest) (*RegionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReplaceRegionCountries not implemented") return nil, status.Error(codes.Unimplemented, "method ReplaceRegionCountries not implemented")
} }
func (UnimplementedRegionAdminServiceServer) mustEmbedUnimplementedRegionAdminServiceServer() {} func (UnimplementedRegionAdminServiceServer) mustEmbedUnimplementedRegionAdminServiceServer() {}
func (UnimplementedRegionAdminServiceServer) testEmbeddedByValue() {} func (UnimplementedRegionAdminServiceServer) testEmbeddedByValue() {}
@ -3024,7 +3024,7 @@ type UnsafeRegionAdminServiceServer interface {
} }
func RegisterRegionAdminServiceServer(s grpc.ServiceRegistrar, srv RegionAdminServiceServer) { func RegisterRegionAdminServiceServer(s grpc.ServiceRegistrar, srv RegionAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedRegionAdminServiceServer was // If the following call panics, it indicates UnimplementedRegionAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -3261,25 +3261,25 @@ type UserIdentityServiceServer interface {
type UnimplementedUserIdentityServiceServer struct{} type UnimplementedUserIdentityServiceServer struct{}
func (UnimplementedUserIdentityServiceServer) GetUserIdentity(context.Context, *GetUserIdentityRequest) (*GetUserIdentityResponse, error) { func (UnimplementedUserIdentityServiceServer) GetUserIdentity(context.Context, *GetUserIdentityRequest) (*GetUserIdentityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserIdentity not implemented") return nil, status.Error(codes.Unimplemented, "method GetUserIdentity not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ResolveDisplayUserID(context.Context, *ResolveDisplayUserIDRequest) (*ResolveDisplayUserIDResponse, error) { func (UnimplementedUserIdentityServiceServer) ResolveDisplayUserID(context.Context, *ResolveDisplayUserIDRequest) (*ResolveDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResolveDisplayUserID not implemented") return nil, status.Error(codes.Unimplemented, "method ResolveDisplayUserID not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ChangeDisplayUserID(context.Context, *ChangeDisplayUserIDRequest) (*ChangeDisplayUserIDResponse, error) { func (UnimplementedUserIdentityServiceServer) ChangeDisplayUserID(context.Context, *ChangeDisplayUserIDRequest) (*ChangeDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangeDisplayUserID not implemented") return nil, status.Error(codes.Unimplemented, "method ChangeDisplayUserID not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) { func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayUserID(context.Context, *ApplyPrettyDisplayUserIDRequest) (*ApplyPrettyDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayUserID not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ListAvailablePrettyDisplayIDs(context.Context, *ListAvailablePrettyDisplayIDsRequest) (*ListAvailablePrettyDisplayIDsResponse, error) { func (UnimplementedUserIdentityServiceServer) ListAvailablePrettyDisplayIDs(context.Context, *ListAvailablePrettyDisplayIDsRequest) (*ListAvailablePrettyDisplayIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAvailablePrettyDisplayIDs not implemented") return nil, status.Error(codes.Unimplemented, "method ListAvailablePrettyDisplayIDs not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayIDFromPool(context.Context, *ApplyPrettyDisplayIDFromPoolRequest) (*ApplyPrettyDisplayIDFromPoolResponse, error) { func (UnimplementedUserIdentityServiceServer) ApplyPrettyDisplayIDFromPool(context.Context, *ApplyPrettyDisplayIDFromPoolRequest) (*ApplyPrettyDisplayIDFromPoolResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyPrettyDisplayIDFromPool not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyPrettyDisplayIDFromPool not implemented")
} }
func (UnimplementedUserIdentityServiceServer) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) { func (UnimplementedUserIdentityServiceServer) ExpirePrettyDisplayUserID(context.Context, *ExpirePrettyDisplayUserIDRequest) (*ExpirePrettyDisplayUserIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented") return nil, status.Error(codes.Unimplemented, "method ExpirePrettyDisplayUserID not implemented")
} }
func (UnimplementedUserIdentityServiceServer) mustEmbedUnimplementedUserIdentityServiceServer() {} func (UnimplementedUserIdentityServiceServer) mustEmbedUnimplementedUserIdentityServiceServer() {}
func (UnimplementedUserIdentityServiceServer) testEmbeddedByValue() {} func (UnimplementedUserIdentityServiceServer) testEmbeddedByValue() {}
@ -3292,7 +3292,7 @@ type UnsafeUserIdentityServiceServer interface {
} }
func RegisterUserIdentityServiceServer(s grpc.ServiceRegistrar, srv UserIdentityServiceServer) { func RegisterUserIdentityServiceServer(s grpc.ServiceRegistrar, srv UserIdentityServiceServer) {
// If the following call pancis, it indicates UnimplementedUserIdentityServiceServer was // If the following call panics, it indicates UnimplementedUserIdentityServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -3608,28 +3608,28 @@ type UserPrettyDisplayIDAdminServiceServer interface {
type UnimplementedUserPrettyDisplayIDAdminServiceServer struct{} type UnimplementedUserPrettyDisplayIDAdminServiceServer struct{}
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDPools(context.Context, *ListPrettyDisplayIDPoolsRequest) (*ListPrettyDisplayIDPoolsResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDPools(context.Context, *ListPrettyDisplayIDPoolsRequest) (*ListPrettyDisplayIDPoolsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPrettyDisplayIDPools not implemented") return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDPools not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) CreatePrettyDisplayIDPool(context.Context, *CreatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) CreatePrettyDisplayIDPool(context.Context, *CreatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePrettyDisplayIDPool not implemented") return nil, status.Error(codes.Unimplemented, "method CreatePrettyDisplayIDPool not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) UpdatePrettyDisplayIDPool(context.Context, *UpdatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) UpdatePrettyDisplayIDPool(context.Context, *UpdatePrettyDisplayIDPoolRequest) (*PrettyDisplayIDPoolResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdatePrettyDisplayIDPool not implemented") return nil, status.Error(codes.Unimplemented, "method UpdatePrettyDisplayIDPool not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) GeneratePrettyDisplayIDs(context.Context, *GeneratePrettyDisplayIDsRequest) (*GeneratePrettyDisplayIDsResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) GeneratePrettyDisplayIDs(context.Context, *GeneratePrettyDisplayIDsRequest) (*GeneratePrettyDisplayIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GeneratePrettyDisplayIDs not implemented") return nil, status.Error(codes.Unimplemented, "method GeneratePrettyDisplayIDs not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDs(context.Context, *ListPrettyDisplayIDsRequest) (*ListPrettyDisplayIDsResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) ListPrettyDisplayIDs(context.Context, *ListPrettyDisplayIDsRequest) (*ListPrettyDisplayIDsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPrettyDisplayIDs not implemented") return nil, status.Error(codes.Unimplemented, "method ListPrettyDisplayIDs not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) SetPrettyDisplayIDStatus(context.Context, *SetPrettyDisplayIDStatusRequest) (*PrettyDisplayIDResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) SetPrettyDisplayIDStatus(context.Context, *SetPrettyDisplayIDStatusRequest) (*PrettyDisplayIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetPrettyDisplayIDStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetPrettyDisplayIDStatus not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) RecyclePrettyDisplayID(context.Context, *RecyclePrettyDisplayIDRequest) (*PrettyDisplayIDResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) RecyclePrettyDisplayID(context.Context, *RecyclePrettyDisplayIDRequest) (*PrettyDisplayIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RecyclePrettyDisplayID not implemented") return nil, status.Error(codes.Unimplemented, "method RecyclePrettyDisplayID not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) AdminGrantPrettyDisplayID(context.Context, *AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResponse, error) { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) AdminGrantPrettyDisplayID(context.Context, *AdminGrantPrettyDisplayIDRequest) (*AdminGrantPrettyDisplayIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminGrantPrettyDisplayID not implemented") return nil, status.Error(codes.Unimplemented, "method AdminGrantPrettyDisplayID not implemented")
} }
func (UnimplementedUserPrettyDisplayIDAdminServiceServer) mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() { func (UnimplementedUserPrettyDisplayIDAdminServiceServer) mustEmbedUnimplementedUserPrettyDisplayIDAdminServiceServer() {
} }
@ -3643,7 +3643,7 @@ type UnsafeUserPrettyDisplayIDAdminServiceServer interface {
} }
func RegisterUserPrettyDisplayIDAdminServiceServer(s grpc.ServiceRegistrar, srv UserPrettyDisplayIDAdminServiceServer) { func RegisterUserPrettyDisplayIDAdminServiceServer(s grpc.ServiceRegistrar, srv UserPrettyDisplayIDAdminServiceServer) {
// If the following call pancis, it indicates UnimplementedUserPrettyDisplayIDAdminServiceServer was // If the following call panics, it indicates UnimplementedUserPrettyDisplayIDAdminServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.

File diff suppressed because it is too large Load Diff

View File

@ -609,6 +609,45 @@ message ResourceGroup {
int64 updated_at_ms = 12; int64 updated_at_ms = 12;
} }
// ResourceGroupSnapshot owner
// snapshot_id App
message ResourceGroupSnapshot {
string app_code = 1;
string snapshot_id = 2;
string pin_key = 3;
int64 source_group_id = 4;
int64 version_no = 5;
string snapshot_hash = 6;
ResourceGroup group = 7;
int64 created_by_user_id = 8;
int64 created_at_ms = 9;
int64 source_group_updated_at_ms = 10;
// source_content_hash
string source_content_hash = 11;
}
message PinResourceGroupSnapshotRequest {
string request_id = 1;
string app_code = 2;
int64 group_id = 3;
// pin_key key
string pin_key = 4;
int64 operator_user_id = 5;
// wallet Get->Pin
int64 expected_group_updated_at_ms = 6;
// GetResourceGroup owner wallet
string expected_source_content_hash = 7;
// required_region_ids active region wallet gift resource
// pin-time gift config fail-close
repeated int64 required_region_ids = 8;
// required_all_regions=true region_id=0 pin-time gift config
bool required_all_regions = 9;
}
message PinResourceGroupSnapshotResponse {
ResourceGroupSnapshot snapshot = 1;
}
message GiftConfig { message GiftConfig {
string app_code = 1; string app_code = 1;
string gift_id = 2; string gift_id = 2;
@ -682,6 +721,11 @@ message UserResourceEntitlement {
int64 created_at_ms = 12; int64 created_at_ms = 12;
int64 updated_at_ms = 13; int64 updated_at_ms = 13;
bool equipped = 14; bool equipped = 14;
string source_snapshot_id = 15;
string resource_snapshot_hash = 16;
// pinned_gift_configs source_snapshot_id gift wallet
// hash/resource/app App 使legacy entitlement 退
repeated GiftConfig pinned_gift_configs = 17;
} }
message ResourceGrantItem { message ResourceGrantItem {
@ -857,6 +901,8 @@ message GetResourceGroupRequest {
message GetResourceGroupResponse { message GetResourceGroupResponse {
ResourceGroup group = 1; ResourceGroup group = 1;
// groupordered items resource SHA-256
string source_content_hash = 2;
} }
message CreateResourceGroupRequest { message CreateResourceGroupRequest {
@ -1042,6 +1088,18 @@ message GrantResourceGroupRequest {
string grant_source = 7; string grant_source = 7;
} }
message GrantPinnedResourceGroupRequest {
string command_id = 1;
string app_code = 2;
int64 target_user_id = 3;
string snapshot_id = 4;
string reason = 5;
int64 operator_user_id = 6;
string grant_source = 7;
// wallet snapshot_id owner
string expected_snapshot_hash = 8;
}
message RevokeResourceGrantRequest { message RevokeResourceGrantRequest {
string request_id = 1; string request_id = 1;
string app_code = 2; string app_code = 2;
@ -2929,6 +2987,7 @@ service WalletService {
rpc CreateResourceGroup(CreateResourceGroupRequest) returns (ResourceGroupResponse); rpc CreateResourceGroup(CreateResourceGroupRequest) returns (ResourceGroupResponse);
rpc UpdateResourceGroup(UpdateResourceGroupRequest) returns (ResourceGroupResponse); rpc UpdateResourceGroup(UpdateResourceGroupRequest) returns (ResourceGroupResponse);
rpc SetResourceGroupStatus(SetResourceGroupStatusRequest) returns (ResourceGroupResponse); rpc SetResourceGroupStatus(SetResourceGroupStatusRequest) returns (ResourceGroupResponse);
rpc PinResourceGroupSnapshot(PinResourceGroupSnapshotRequest) returns (PinResourceGroupSnapshotResponse);
rpc ListGiftConfigs(ListGiftConfigsRequest) returns (ListGiftConfigsResponse); rpc ListGiftConfigs(ListGiftConfigsRequest) returns (ListGiftConfigsResponse);
rpc ListGiftTypeConfigs(ListGiftTypeConfigsRequest) returns (ListGiftTypeConfigsResponse); rpc ListGiftTypeConfigs(ListGiftTypeConfigsRequest) returns (ListGiftTypeConfigsResponse);
rpc GetGiftCatalogVersion(GetGiftCatalogVersionRequest) returns (GetGiftCatalogVersionResponse); rpc GetGiftCatalogVersion(GetGiftCatalogVersionRequest) returns (GetGiftCatalogVersionResponse);
@ -2940,6 +2999,7 @@ service WalletService {
rpc UpsertGiftTypeConfig(UpsertGiftTypeConfigRequest) returns (GiftTypeConfigResponse); rpc UpsertGiftTypeConfig(UpsertGiftTypeConfigRequest) returns (GiftTypeConfigResponse);
rpc GrantResource(GrantResourceRequest) returns (ResourceGrantResponse); rpc GrantResource(GrantResourceRequest) returns (ResourceGrantResponse);
rpc GrantResourceGroup(GrantResourceGroupRequest) returns (ResourceGrantResponse); rpc GrantResourceGroup(GrantResourceGroupRequest) returns (ResourceGrantResponse);
rpc GrantPinnedResourceGroup(GrantPinnedResourceGroupRequest) returns (ResourceGrantResponse);
rpc RevokeResourceGrant(RevokeResourceGrantRequest) returns (ResourceGrantResponse); rpc RevokeResourceGrant(RevokeResourceGrantRequest) returns (ResourceGrantResponse);
rpc ListUserResources(ListUserResourcesRequest) returns (ListUserResourcesResponse); rpc ListUserResources(ListUserResourcesRequest) returns (ListUserResourcesResponse);
rpc EquipUserResource(EquipUserResourceRequest) returns (EquipUserResourceResponse); rpc EquipUserResource(EquipUserResourceRequest) returns (EquipUserResourceResponse);

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.6.2
// - protoc v7.35.0 // - protoc v5.29.2
// source: proto/wallet/v1/wallet.proto // source: proto/wallet/v1/wallet.proto
package walletv1 package walletv1
@ -106,16 +106,16 @@ type WalletCronServiceServer interface {
type UnimplementedWalletCronServiceServer struct{} type UnimplementedWalletCronServiceServer struct{}
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryDailySettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedWalletCronServiceServer) ProcessHostSalaryDailySettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryDailySettlementBatch not implemented")
} }
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryHalfMonthSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedWalletCronServiceServer) ProcessHostSalaryHalfMonthSettlementBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryHalfMonthSettlementBatch not implemented")
} }
func (UnimplementedWalletCronServiceServer) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) { func (UnimplementedWalletCronServiceServer) ProcessHostSalaryMonthEndBatch(context.Context, *CronBatchRequest) (*CronBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessHostSalaryMonthEndBatch not implemented")
} }
func (UnimplementedWalletCronServiceServer) ProcessVipDailyCoinRebateBatch(context.Context, *ProcessVipDailyCoinRebateBatchRequest) (*ProcessVipDailyCoinRebateBatchResponse, error) { func (UnimplementedWalletCronServiceServer) ProcessVipDailyCoinRebateBatch(context.Context, *ProcessVipDailyCoinRebateBatchRequest) (*ProcessVipDailyCoinRebateBatchResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ProcessVipDailyCoinRebateBatch not implemented") return nil, status.Error(codes.Unimplemented, "method ProcessVipDailyCoinRebateBatch not implemented")
} }
func (UnimplementedWalletCronServiceServer) mustEmbedUnimplementedWalletCronServiceServer() {} func (UnimplementedWalletCronServiceServer) mustEmbedUnimplementedWalletCronServiceServer() {}
func (UnimplementedWalletCronServiceServer) testEmbeddedByValue() {} func (UnimplementedWalletCronServiceServer) testEmbeddedByValue() {}
@ -128,7 +128,7 @@ type UnsafeWalletCronServiceServer interface {
} }
func RegisterWalletCronServiceServer(s grpc.ServiceRegistrar, srv WalletCronServiceServer) { func RegisterWalletCronServiceServer(s grpc.ServiceRegistrar, srv WalletCronServiceServer) {
// If the following call pancis, it indicates UnimplementedWalletCronServiceServer was // If the following call panics, it indicates UnimplementedWalletCronServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -278,6 +278,7 @@ const (
WalletService_CreateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResourceGroup" WalletService_CreateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateResourceGroup"
WalletService_UpdateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateResourceGroup" WalletService_UpdateResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateResourceGroup"
WalletService_SetResourceGroupStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetResourceGroupStatus" WalletService_SetResourceGroupStatus_FullMethodName = "/hyapp.wallet.v1.WalletService/SetResourceGroupStatus"
WalletService_PinResourceGroupSnapshot_FullMethodName = "/hyapp.wallet.v1.WalletService/PinResourceGroupSnapshot"
WalletService_ListGiftConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftConfigs" WalletService_ListGiftConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftConfigs"
WalletService_ListGiftTypeConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftTypeConfigs" WalletService_ListGiftTypeConfigs_FullMethodName = "/hyapp.wallet.v1.WalletService/ListGiftTypeConfigs"
WalletService_GetGiftCatalogVersion_FullMethodName = "/hyapp.wallet.v1.WalletService/GetGiftCatalogVersion" WalletService_GetGiftCatalogVersion_FullMethodName = "/hyapp.wallet.v1.WalletService/GetGiftCatalogVersion"
@ -289,6 +290,7 @@ const (
WalletService_UpsertGiftTypeConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpsertGiftTypeConfig" WalletService_UpsertGiftTypeConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpsertGiftTypeConfig"
WalletService_GrantResource_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResource" WalletService_GrantResource_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResource"
WalletService_GrantResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResourceGroup" WalletService_GrantResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantResourceGroup"
WalletService_GrantPinnedResourceGroup_FullMethodName = "/hyapp.wallet.v1.WalletService/GrantPinnedResourceGroup"
WalletService_RevokeResourceGrant_FullMethodName = "/hyapp.wallet.v1.WalletService/RevokeResourceGrant" WalletService_RevokeResourceGrant_FullMethodName = "/hyapp.wallet.v1.WalletService/RevokeResourceGrant"
WalletService_ListUserResources_FullMethodName = "/hyapp.wallet.v1.WalletService/ListUserResources" WalletService_ListUserResources_FullMethodName = "/hyapp.wallet.v1.WalletService/ListUserResources"
WalletService_EquipUserResource_FullMethodName = "/hyapp.wallet.v1.WalletService/EquipUserResource" WalletService_EquipUserResource_FullMethodName = "/hyapp.wallet.v1.WalletService/EquipUserResource"
@ -411,6 +413,7 @@ type WalletServiceClient interface {
CreateResourceGroup(ctx context.Context, in *CreateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error) CreateResourceGroup(ctx context.Context, in *CreateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error)
UpdateResourceGroup(ctx context.Context, in *UpdateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error) UpdateResourceGroup(ctx context.Context, in *UpdateResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error)
SetResourceGroupStatus(ctx context.Context, in *SetResourceGroupStatusRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error) SetResourceGroupStatus(ctx context.Context, in *SetResourceGroupStatusRequest, opts ...grpc.CallOption) (*ResourceGroupResponse, error)
PinResourceGroupSnapshot(ctx context.Context, in *PinResourceGroupSnapshotRequest, opts ...grpc.CallOption) (*PinResourceGroupSnapshotResponse, error)
ListGiftConfigs(ctx context.Context, in *ListGiftConfigsRequest, opts ...grpc.CallOption) (*ListGiftConfigsResponse, error) ListGiftConfigs(ctx context.Context, in *ListGiftConfigsRequest, opts ...grpc.CallOption) (*ListGiftConfigsResponse, error)
ListGiftTypeConfigs(ctx context.Context, in *ListGiftTypeConfigsRequest, opts ...grpc.CallOption) (*ListGiftTypeConfigsResponse, error) ListGiftTypeConfigs(ctx context.Context, in *ListGiftTypeConfigsRequest, opts ...grpc.CallOption) (*ListGiftTypeConfigsResponse, error)
GetGiftCatalogVersion(ctx context.Context, in *GetGiftCatalogVersionRequest, opts ...grpc.CallOption) (*GetGiftCatalogVersionResponse, error) GetGiftCatalogVersion(ctx context.Context, in *GetGiftCatalogVersionRequest, opts ...grpc.CallOption) (*GetGiftCatalogVersionResponse, error)
@ -422,6 +425,7 @@ type WalletServiceClient interface {
UpsertGiftTypeConfig(ctx context.Context, in *UpsertGiftTypeConfigRequest, opts ...grpc.CallOption) (*GiftTypeConfigResponse, error) UpsertGiftTypeConfig(ctx context.Context, in *UpsertGiftTypeConfigRequest, opts ...grpc.CallOption) (*GiftTypeConfigResponse, error)
GrantResource(ctx context.Context, in *GrantResourceRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) GrantResource(ctx context.Context, in *GrantResourceRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error)
GrantResourceGroup(ctx context.Context, in *GrantResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) GrantResourceGroup(ctx context.Context, in *GrantResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error)
GrantPinnedResourceGroup(ctx context.Context, in *GrantPinnedResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error)
RevokeResourceGrant(ctx context.Context, in *RevokeResourceGrantRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) RevokeResourceGrant(ctx context.Context, in *RevokeResourceGrantRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error)
ListUserResources(ctx context.Context, in *ListUserResourcesRequest, opts ...grpc.CallOption) (*ListUserResourcesResponse, error) ListUserResources(ctx context.Context, in *ListUserResourcesRequest, opts ...grpc.CallOption) (*ListUserResourcesResponse, error)
EquipUserResource(ctx context.Context, in *EquipUserResourceRequest, opts ...grpc.CallOption) (*EquipUserResourceResponse, error) EquipUserResource(ctx context.Context, in *EquipUserResourceRequest, opts ...grpc.CallOption) (*EquipUserResourceResponse, error)
@ -896,6 +900,16 @@ func (c *walletServiceClient) SetResourceGroupStatus(ctx context.Context, in *Se
return out, nil return out, nil
} }
func (c *walletServiceClient) PinResourceGroupSnapshot(ctx context.Context, in *PinResourceGroupSnapshotRequest, opts ...grpc.CallOption) (*PinResourceGroupSnapshotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PinResourceGroupSnapshotResponse)
err := c.cc.Invoke(ctx, WalletService_PinResourceGroupSnapshot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) ListGiftConfigs(ctx context.Context, in *ListGiftConfigsRequest, opts ...grpc.CallOption) (*ListGiftConfigsResponse, error) { func (c *walletServiceClient) ListGiftConfigs(ctx context.Context, in *ListGiftConfigsRequest, opts ...grpc.CallOption) (*ListGiftConfigsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListGiftConfigsResponse) out := new(ListGiftConfigsResponse)
@ -1006,6 +1020,16 @@ func (c *walletServiceClient) GrantResourceGroup(ctx context.Context, in *GrantR
return out, nil return out, nil
} }
func (c *walletServiceClient) GrantPinnedResourceGroup(ctx context.Context, in *GrantPinnedResourceGroupRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceGrantResponse)
err := c.cc.Invoke(ctx, WalletService_GrantPinnedResourceGroup_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) RevokeResourceGrant(ctx context.Context, in *RevokeResourceGrantRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) { func (c *walletServiceClient) RevokeResourceGrant(ctx context.Context, in *RevokeResourceGrantRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResourceGrantResponse) out := new(ResourceGrantResponse)
@ -1792,6 +1816,7 @@ type WalletServiceServer interface {
CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error)
UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error) UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error)
SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error) SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error)
PinResourceGroupSnapshot(context.Context, *PinResourceGroupSnapshotRequest) (*PinResourceGroupSnapshotResponse, error)
ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error) ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error)
ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error)
GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error) GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error)
@ -1803,6 +1828,7 @@ type WalletServiceServer interface {
UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error)
GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error)
GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error)
GrantPinnedResourceGroup(context.Context, *GrantPinnedResourceGroupRequest) (*ResourceGrantResponse, error)
RevokeResourceGrant(context.Context, *RevokeResourceGrantRequest) (*ResourceGrantResponse, error) RevokeResourceGrant(context.Context, *RevokeResourceGrantRequest) (*ResourceGrantResponse, error)
ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error) ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error)
EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error) EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error)
@ -1888,376 +1914,382 @@ type WalletServiceServer interface {
type UnimplementedWalletServiceServer struct{} type UnimplementedWalletServiceServer struct{}
func (UnimplementedWalletServiceServer) DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) { func (UnimplementedWalletServiceServer) DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitGift not implemented") return nil, status.Error(codes.Unimplemented, "method DebitGift not implemented")
} }
func (UnimplementedWalletServiceServer) DebitDirectGift(context.Context, *DebitDirectGiftRequest) (*DebitGiftResponse, error) { func (UnimplementedWalletServiceServer) DebitDirectGift(context.Context, *DebitDirectGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitDirectGift not implemented") return nil, status.Error(codes.Unimplemented, "method DebitDirectGift not implemented")
} }
func (UnimplementedWalletServiceServer) BatchDebitGift(context.Context, *BatchDebitGiftRequest) (*BatchDebitGiftResponse, error) { func (UnimplementedWalletServiceServer) BatchDebitGift(context.Context, *BatchDebitGiftRequest) (*BatchDebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchDebitGift not implemented") return nil, status.Error(codes.Unimplemented, "method BatchDebitGift not implemented")
} }
func (UnimplementedWalletServiceServer) DebitRobotGift(context.Context, *DebitRobotGiftRequest) (*DebitGiftResponse, error) { func (UnimplementedWalletServiceServer) DebitRobotGift(context.Context, *DebitRobotGiftRequest) (*DebitGiftResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitRobotGift not implemented") return nil, status.Error(codes.Unimplemented, "method DebitRobotGift not implemented")
} }
func (UnimplementedWalletServiceServer) GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) { func (UnimplementedWalletServiceServer) GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBalances not implemented") return nil, status.Error(codes.Unimplemented, "method GetBalances not implemented")
} }
func (UnimplementedWalletServiceServer) GetActiveHostSalaryPolicy(context.Context, *GetActiveHostSalaryPolicyRequest) (*GetActiveHostSalaryPolicyResponse, error) { func (UnimplementedWalletServiceServer) GetActiveHostSalaryPolicy(context.Context, *GetActiveHostSalaryPolicyRequest) (*GetActiveHostSalaryPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented") return nil, status.Error(codes.Unimplemented, "method GetActiveHostSalaryPolicy not implemented")
} }
func (UnimplementedWalletServiceServer) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) { func (UnimplementedWalletServiceServer) GetHostSalaryProgress(context.Context, *GetHostSalaryProgressRequest) (*GetHostSalaryProgressResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostSalaryProgress not implemented") return nil, status.Error(codes.Unimplemented, "method GetHostSalaryProgress not implemented")
} }
func (UnimplementedWalletServiceServer) GetHostRevenueStats(context.Context, *GetHostRevenueStatsRequest) (*GetHostRevenueStatsResponse, error) { func (UnimplementedWalletServiceServer) GetHostRevenueStats(context.Context, *GetHostRevenueStatsRequest) (*GetHostRevenueStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetHostRevenueStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetHostRevenueStats not implemented")
} }
func (UnimplementedWalletServiceServer) GetAgencyPointShareStats(context.Context, *GetAgencyPointShareStatsRequest) (*GetAgencyPointShareStatsResponse, error) { func (UnimplementedWalletServiceServer) GetAgencyPointShareStats(context.Context, *GetAgencyPointShareStatsRequest) (*GetAgencyPointShareStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyPointShareStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetAgencyPointShareStats not implemented")
} }
func (UnimplementedWalletServiceServer) GetAgencyHostGiftStats(context.Context, *GetAgencyHostGiftStatsRequest) (*GetAgencyHostGiftStatsResponse, error) { func (UnimplementedWalletServiceServer) GetAgencyHostGiftStats(context.Context, *GetAgencyHostGiftStatsRequest) (*GetAgencyHostGiftStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAgencyHostGiftStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetAgencyHostGiftStats not implemented")
} }
func (UnimplementedWalletServiceServer) GetTeamHostSalaryStats(context.Context, *GetTeamHostSalaryStatsRequest) (*GetTeamHostSalaryStatsResponse, error) { func (UnimplementedWalletServiceServer) GetTeamHostSalaryStats(context.Context, *GetTeamHostSalaryStatsRequest) (*GetTeamHostSalaryStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTeamHostSalaryStats not implemented") return nil, status.Error(codes.Unimplemented, "method GetTeamHostSalaryStats not implemented")
} }
func (UnimplementedWalletServiceServer) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) { func (UnimplementedWalletServiceServer) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditAsset not implemented") return nil, status.Error(codes.Unimplemented, "method AdminCreditAsset not implemented")
} }
func (UnimplementedWalletServiceServer) AdminCreditCoinSellerStock(context.Context, *AdminCreditCoinSellerStockRequest) (*AdminCreditCoinSellerStockResponse, error) { func (UnimplementedWalletServiceServer) AdminCreditCoinSellerStock(context.Context, *AdminCreditCoinSellerStockRequest) (*AdminCreditCoinSellerStockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented") return nil, status.Error(codes.Unimplemented, "method AdminCreditCoinSellerStock not implemented")
} }
func (UnimplementedWalletServiceServer) TransferCoinFromSeller(context.Context, *TransferCoinFromSellerRequest) (*TransferCoinFromSellerResponse, error) { func (UnimplementedWalletServiceServer) TransferCoinFromSeller(context.Context, *TransferCoinFromSellerRequest) (*TransferCoinFromSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferCoinFromSeller not implemented") return nil, status.Error(codes.Unimplemented, "method TransferCoinFromSeller not implemented")
} }
func (UnimplementedWalletServiceServer) TransferCoinSellerStockToChild(context.Context, *TransferCoinSellerStockToChildRequest) (*TransferCoinSellerStockToChildResponse, error) { func (UnimplementedWalletServiceServer) TransferCoinSellerStockToChild(context.Context, *TransferCoinSellerStockToChildRequest) (*TransferCoinSellerStockToChildResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferCoinSellerStockToChild not implemented") return nil, status.Error(codes.Unimplemented, "method TransferCoinSellerStockToChild not implemented")
} }
func (UnimplementedWalletServiceServer) ListCoinSellerSalaryExchangeRateTiers(context.Context, *ListCoinSellerSalaryExchangeRateTiersRequest) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) { func (UnimplementedWalletServiceServer) ListCoinSellerSalaryExchangeRateTiers(context.Context, *ListCoinSellerSalaryExchangeRateTiersRequest) (*ListCoinSellerSalaryExchangeRateTiersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented") return nil, status.Error(codes.Unimplemented, "method ListCoinSellerSalaryExchangeRateTiers not implemented")
} }
func (UnimplementedWalletServiceServer) ExchangeSalaryToCoin(context.Context, *ExchangeSalaryToCoinRequest) (*ExchangeSalaryToCoinResponse, error) { func (UnimplementedWalletServiceServer) ExchangeSalaryToCoin(context.Context, *ExchangeSalaryToCoinRequest) (*ExchangeSalaryToCoinResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented") return nil, status.Error(codes.Unimplemented, "method ExchangeSalaryToCoin not implemented")
} }
func (UnimplementedWalletServiceServer) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) { func (UnimplementedWalletServiceServer) TransferSalaryToCoinSeller(context.Context, *TransferSalaryToCoinSellerRequest) (*TransferSalaryToCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented") return nil, status.Error(codes.Unimplemented, "method TransferSalaryToCoinSeller not implemented")
} }
func (UnimplementedWalletServiceServer) ListPointWithdrawalCoinSellers(context.Context, *ListPointWithdrawalCoinSellersRequest) (*ListPointWithdrawalCoinSellersResponse, error) { func (UnimplementedWalletServiceServer) ListPointWithdrawalCoinSellers(context.Context, *ListPointWithdrawalCoinSellersRequest) (*ListPointWithdrawalCoinSellersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPointWithdrawalCoinSellers not implemented") return nil, status.Error(codes.Unimplemented, "method ListPointWithdrawalCoinSellers not implemented")
} }
func (UnimplementedWalletServiceServer) TransferPointToCoinSeller(context.Context, *TransferPointToCoinSellerRequest) (*TransferPointToCoinSellerResponse, error) { func (UnimplementedWalletServiceServer) TransferPointToCoinSeller(context.Context, *TransferPointToCoinSellerRequest) (*TransferPointToCoinSellerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TransferPointToCoinSeller not implemented") return nil, status.Error(codes.Unimplemented, "method TransferPointToCoinSeller not implemented")
} }
func (UnimplementedWalletServiceServer) GetPointWithdrawalConfig(context.Context, *GetPointWithdrawalConfigRequest) (*GetPointWithdrawalConfigResponse, error) { func (UnimplementedWalletServiceServer) GetPointWithdrawalConfig(context.Context, *GetPointWithdrawalConfigRequest) (*GetPointWithdrawalConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPointWithdrawalConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetPointWithdrawalConfig not implemented")
} }
func (UnimplementedWalletServiceServer) FreezeSalaryWithdrawal(context.Context, *FreezeSalaryWithdrawalRequest) (*FreezeSalaryWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) FreezeSalaryWithdrawal(context.Context, *FreezeSalaryWithdrawalRequest) (*FreezeSalaryWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FreezeSalaryWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method FreezeSalaryWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) SettleSalaryWithdrawal(context.Context, *SettleSalaryWithdrawalRequest) (*SettleSalaryWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) SettleSalaryWithdrawal(context.Context, *SettleSalaryWithdrawalRequest) (*SettleSalaryWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SettleSalaryWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method SettleSalaryWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) ReleaseSalaryWithdrawal(context.Context, *ReleaseSalaryWithdrawalRequest) (*ReleaseSalaryWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) ReleaseSalaryWithdrawal(context.Context, *ReleaseSalaryWithdrawalRequest) (*ReleaseSalaryWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReleaseSalaryWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method ReleaseSalaryWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) FreezePointWithdrawal(context.Context, *FreezePointWithdrawalRequest) (*FreezePointWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) FreezePointWithdrawal(context.Context, *FreezePointWithdrawalRequest) (*FreezePointWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FreezePointWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method FreezePointWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) SettlePointWithdrawal(context.Context, *SettlePointWithdrawalRequest) (*SettlePointWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) SettlePointWithdrawal(context.Context, *SettlePointWithdrawalRequest) (*SettlePointWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SettlePointWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method SettlePointWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) ReleasePointWithdrawal(context.Context, *ReleasePointWithdrawalRequest) (*ReleasePointWithdrawalResponse, error) { func (UnimplementedWalletServiceServer) ReleasePointWithdrawal(context.Context, *ReleasePointWithdrawalRequest) (*ReleasePointWithdrawalResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReleasePointWithdrawal not implemented") return nil, status.Error(codes.Unimplemented, "method ReleasePointWithdrawal not implemented")
} }
func (UnimplementedWalletServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) { func (UnimplementedWalletServiceServer) ListResources(context.Context, *ListResourcesRequest) (*ListResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResources not implemented") return nil, status.Error(codes.Unimplemented, "method ListResources not implemented")
} }
func (UnimplementedWalletServiceServer) GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) { func (UnimplementedWalletServiceServer) GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented") return nil, status.Error(codes.Unimplemented, "method GetResource not implemented")
} }
func (UnimplementedWalletServiceServer) CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error) { func (UnimplementedWalletServiceServer) CreateResource(context.Context, *CreateResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateResource not implemented") return nil, status.Error(codes.Unimplemented, "method CreateResource not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error) { func (UnimplementedWalletServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateResource not implemented")
} }
func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) { func (UnimplementedWalletServiceServer) SetResourceStatus(context.Context, *SetResourceStatusRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetResourceStatus not implemented")
} }
func (UnimplementedWalletServiceServer) DeleteResource(context.Context, *DeleteResourceRequest) (*ResourceResponse, error) { func (UnimplementedWalletServiceServer) DeleteResource(context.Context, *DeleteResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteResource not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteResource not implemented")
} }
func (UnimplementedWalletServiceServer) BatchDeleteResources(context.Context, *BatchDeleteResourcesRequest) (*BatchDeleteResourcesResponse, error) { func (UnimplementedWalletServiceServer) BatchDeleteResources(context.Context, *BatchDeleteResourcesRequest) (*BatchDeleteResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchDeleteResources not implemented") return nil, status.Error(codes.Unimplemented, "method BatchDeleteResources not implemented")
} }
func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) { func (UnimplementedWalletServiceServer) ListResourceGroups(context.Context, *ListResourceGroupsRequest) (*ListResourceGroupsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGroups not implemented") return nil, status.Error(codes.Unimplemented, "method ListResourceGroups not implemented")
} }
func (UnimplementedWalletServiceServer) GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error) { func (UnimplementedWalletServiceServer) GetResourceGroup(context.Context, *GetResourceGroupRequest) (*GetResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResourceGroup not implemented") return nil, status.Error(codes.Unimplemented, "method GetResourceGroup not implemented")
} }
func (UnimplementedWalletServiceServer) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) { func (UnimplementedWalletServiceServer) CreateResourceGroup(context.Context, *CreateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateResourceGroup not implemented") return nil, status.Error(codes.Unimplemented, "method CreateResourceGroup not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error) { func (UnimplementedWalletServiceServer) UpdateResourceGroup(context.Context, *UpdateResourceGroupRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResourceGroup not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateResourceGroup not implemented")
} }
func (UnimplementedWalletServiceServer) SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error) { func (UnimplementedWalletServiceServer) SetResourceGroupStatus(context.Context, *SetResourceGroupStatusRequest) (*ResourceGroupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceGroupStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetResourceGroupStatus not implemented")
}
func (UnimplementedWalletServiceServer) PinResourceGroupSnapshot(context.Context, *PinResourceGroupSnapshotRequest) (*PinResourceGroupSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method PinResourceGroupSnapshot not implemented")
} }
func (UnimplementedWalletServiceServer) ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error) { func (UnimplementedWalletServiceServer) ListGiftConfigs(context.Context, *ListGiftConfigsRequest) (*ListGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGiftConfigs not implemented") return nil, status.Error(codes.Unimplemented, "method ListGiftConfigs not implemented")
} }
func (UnimplementedWalletServiceServer) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) { func (UnimplementedWalletServiceServer) ListGiftTypeConfigs(context.Context, *ListGiftTypeConfigsRequest) (*ListGiftTypeConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListGiftTypeConfigs not implemented") return nil, status.Error(codes.Unimplemented, "method ListGiftTypeConfigs not implemented")
} }
func (UnimplementedWalletServiceServer) GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error) { func (UnimplementedWalletServiceServer) GetGiftCatalogVersion(context.Context, *GetGiftCatalogVersionRequest) (*GetGiftCatalogVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetGiftCatalogVersion not implemented") return nil, status.Error(codes.Unimplemented, "method GetGiftCatalogVersion not implemented")
} }
func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) CreateGiftConfig(context.Context, *CreateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateGiftConfig not implemented") return nil, status.Error(codes.Unimplemented, "method CreateGiftConfig not implemented")
} }
func (UnimplementedWalletServiceServer) BatchCreateGiftConfigs(context.Context, *BatchCreateGiftConfigsRequest) (*BatchCreateGiftConfigsResponse, error) { func (UnimplementedWalletServiceServer) BatchCreateGiftConfigs(context.Context, *BatchCreateGiftConfigsRequest) (*BatchCreateGiftConfigsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchCreateGiftConfigs not implemented") return nil, status.Error(codes.Unimplemented, "method BatchCreateGiftConfigs not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) UpdateGiftConfig(context.Context, *UpdateGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateGiftConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateGiftConfig not implemented")
} }
func (UnimplementedWalletServiceServer) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) SetGiftConfigStatus(context.Context, *SetGiftConfigStatusRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetGiftConfigStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetGiftConfigStatus not implemented")
} }
func (UnimplementedWalletServiceServer) DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) { func (UnimplementedWalletServiceServer) DeleteGiftConfig(context.Context, *DeleteGiftConfigRequest) (*GiftConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteGiftConfig not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteGiftConfig not implemented")
} }
func (UnimplementedWalletServiceServer) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) { func (UnimplementedWalletServiceServer) UpsertGiftTypeConfig(context.Context, *UpsertGiftTypeConfigRequest) (*GiftTypeConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpsertGiftTypeConfig not implemented")
} }
func (UnimplementedWalletServiceServer) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) { func (UnimplementedWalletServiceServer) GrantResource(context.Context, *GrantResourceRequest) (*ResourceGrantResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantResource not implemented") return nil, status.Error(codes.Unimplemented, "method GrantResource not implemented")
} }
func (UnimplementedWalletServiceServer) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) { func (UnimplementedWalletServiceServer) GrantResourceGroup(context.Context, *GrantResourceGroupRequest) (*ResourceGrantResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantResourceGroup not implemented") return nil, status.Error(codes.Unimplemented, "method GrantResourceGroup not implemented")
}
func (UnimplementedWalletServiceServer) GrantPinnedResourceGroup(context.Context, *GrantPinnedResourceGroupRequest) (*ResourceGrantResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GrantPinnedResourceGroup not implemented")
} }
func (UnimplementedWalletServiceServer) RevokeResourceGrant(context.Context, *RevokeResourceGrantRequest) (*ResourceGrantResponse, error) { func (UnimplementedWalletServiceServer) RevokeResourceGrant(context.Context, *RevokeResourceGrantRequest) (*ResourceGrantResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RevokeResourceGrant not implemented") return nil, status.Error(codes.Unimplemented, "method RevokeResourceGrant not implemented")
} }
func (UnimplementedWalletServiceServer) ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error) { func (UnimplementedWalletServiceServer) ListUserResources(context.Context, *ListUserResourcesRequest) (*ListUserResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserResources not implemented") return nil, status.Error(codes.Unimplemented, "method ListUserResources not implemented")
} }
func (UnimplementedWalletServiceServer) EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error) { func (UnimplementedWalletServiceServer) EquipUserResource(context.Context, *EquipUserResourceRequest) (*EquipUserResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EquipUserResource not implemented") return nil, status.Error(codes.Unimplemented, "method EquipUserResource not implemented")
} }
func (UnimplementedWalletServiceServer) UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error) { func (UnimplementedWalletServiceServer) UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnequipUserResource not implemented") return nil, status.Error(codes.Unimplemented, "method UnequipUserResource not implemented")
} }
func (UnimplementedWalletServiceServer) BatchGetUserEquippedResources(context.Context, *BatchGetUserEquippedResourcesRequest) (*BatchGetUserEquippedResourcesResponse, error) { func (UnimplementedWalletServiceServer) BatchGetUserEquippedResources(context.Context, *BatchGetUserEquippedResourcesRequest) (*BatchGetUserEquippedResourcesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetUserEquippedResources not implemented")
} }
func (UnimplementedWalletServiceServer) ListResourceGrants(context.Context, *ListResourceGrantsRequest) (*ListResourceGrantsResponse, error) { func (UnimplementedWalletServiceServer) ListResourceGrants(context.Context, *ListResourceGrantsRequest) (*ListResourceGrantsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceGrants not implemented") return nil, status.Error(codes.Unimplemented, "method ListResourceGrants not implemented")
} }
func (UnimplementedWalletServiceServer) ListResourceShopItems(context.Context, *ListResourceShopItemsRequest) (*ListResourceShopItemsResponse, error) { func (UnimplementedWalletServiceServer) ListResourceShopItems(context.Context, *ListResourceShopItemsRequest) (*ListResourceShopItemsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceShopItems not implemented") return nil, status.Error(codes.Unimplemented, "method ListResourceShopItems not implemented")
} }
func (UnimplementedWalletServiceServer) UpsertResourceShopItems(context.Context, *UpsertResourceShopItemsRequest) (*UpsertResourceShopItemsResponse, error) { func (UnimplementedWalletServiceServer) UpsertResourceShopItems(context.Context, *UpsertResourceShopItemsRequest) (*UpsertResourceShopItemsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpsertResourceShopItems not implemented") return nil, status.Error(codes.Unimplemented, "method UpsertResourceShopItems not implemented")
} }
func (UnimplementedWalletServiceServer) SetResourceShopItemStatus(context.Context, *SetResourceShopItemStatusRequest) (*ResourceShopItemResponse, error) { func (UnimplementedWalletServiceServer) SetResourceShopItemStatus(context.Context, *SetResourceShopItemStatusRequest) (*ResourceShopItemResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetResourceShopItemStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetResourceShopItemStatus not implemented")
} }
func (UnimplementedWalletServiceServer) ListResourceShopPurchaseOrders(context.Context, *ListResourceShopPurchaseOrdersRequest) (*ListResourceShopPurchaseOrdersResponse, error) { func (UnimplementedWalletServiceServer) ListResourceShopPurchaseOrders(context.Context, *ListResourceShopPurchaseOrdersRequest) (*ListResourceShopPurchaseOrdersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResourceShopPurchaseOrders not implemented") return nil, status.Error(codes.Unimplemented, "method ListResourceShopPurchaseOrders not implemented")
} }
func (UnimplementedWalletServiceServer) PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) { func (UnimplementedWalletServiceServer) PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PurchaseResourceShopItem not implemented") return nil, status.Error(codes.Unimplemented, "method PurchaseResourceShopItem not implemented")
} }
func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) { func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeBills not implemented") return nil, status.Error(codes.Unimplemented, "method ListRechargeBills not implemented")
} }
func (UnimplementedWalletServiceServer) GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error) { func (UnimplementedWalletServiceServer) GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRechargeBillSummary not implemented") return nil, status.Error(codes.Unimplemented, "method GetRechargeBillSummary not implemented")
} }
func (UnimplementedWalletServiceServer) BatchGetUserRechargeStats(context.Context, *BatchGetUserRechargeStatsRequest) (*BatchGetUserRechargeStatsResponse, error) { func (UnimplementedWalletServiceServer) BatchGetUserRechargeStats(context.Context, *BatchGetUserRechargeStatsRequest) (*BatchGetUserRechargeStatsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BatchGetUserRechargeStats not implemented") return nil, status.Error(codes.Unimplemented, "method BatchGetUserRechargeStats not implemented")
} }
func (UnimplementedWalletServiceServer) GetRechargeBillOverview(context.Context, *GetRechargeBillOverviewRequest) (*GetRechargeBillOverviewResponse, error) { func (UnimplementedWalletServiceServer) GetRechargeBillOverview(context.Context, *GetRechargeBillOverviewRequest) (*GetRechargeBillOverviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRechargeBillOverview not implemented") return nil, status.Error(codes.Unimplemented, "method GetRechargeBillOverview not implemented")
} }
func (UnimplementedWalletServiceServer) RefreshGooglePaymentPrices(context.Context, *RefreshGooglePaymentPricesRequest) (*RefreshGooglePaymentPricesResponse, error) { func (UnimplementedWalletServiceServer) RefreshGooglePaymentPrices(context.Context, *RefreshGooglePaymentPricesRequest) (*RefreshGooglePaymentPricesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshGooglePaymentPrices not implemented") return nil, status.Error(codes.Unimplemented, "method RefreshGooglePaymentPrices not implemented")
} }
func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) { func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWalletOverview not implemented") return nil, status.Error(codes.Unimplemented, "method GetWalletOverview not implemented")
} }
func (UnimplementedWalletServiceServer) GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error) { func (UnimplementedWalletServiceServer) GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWalletValueSummary not implemented") return nil, status.Error(codes.Unimplemented, "method GetWalletValueSummary not implemented")
} }
func (UnimplementedWalletServiceServer) GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error) { func (UnimplementedWalletServiceServer) GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserGiftWall not implemented") return nil, status.Error(codes.Unimplemented, "method GetUserGiftWall not implemented")
} }
func (UnimplementedWalletServiceServer) ListRechargeProducts(context.Context, *ListRechargeProductsRequest) (*ListRechargeProductsResponse, error) { func (UnimplementedWalletServiceServer) ListRechargeProducts(context.Context, *ListRechargeProductsRequest) (*ListRechargeProductsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeProducts not implemented") return nil, status.Error(codes.Unimplemented, "method ListRechargeProducts not implemented")
} }
func (UnimplementedWalletServiceServer) ConfirmGooglePayment(context.Context, *ConfirmGooglePaymentRequest) (*ConfirmGooglePaymentResponse, error) { func (UnimplementedWalletServiceServer) ConfirmGooglePayment(context.Context, *ConfirmGooglePaymentRequest) (*ConfirmGooglePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmGooglePayment not implemented") return nil, status.Error(codes.Unimplemented, "method ConfirmGooglePayment not implemented")
} }
func (UnimplementedWalletServiceServer) ListAdminRechargeProducts(context.Context, *ListAdminRechargeProductsRequest) (*ListAdminRechargeProductsResponse, error) { func (UnimplementedWalletServiceServer) ListAdminRechargeProducts(context.Context, *ListAdminRechargeProductsRequest) (*ListAdminRechargeProductsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAdminRechargeProducts not implemented") return nil, status.Error(codes.Unimplemented, "method ListAdminRechargeProducts not implemented")
} }
func (UnimplementedWalletServiceServer) CreateRechargeProduct(context.Context, *CreateRechargeProductRequest) (*RechargeProductResponse, error) { func (UnimplementedWalletServiceServer) CreateRechargeProduct(context.Context, *CreateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRechargeProduct not implemented") return nil, status.Error(codes.Unimplemented, "method CreateRechargeProduct not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateRechargeProduct(context.Context, *UpdateRechargeProductRequest) (*RechargeProductResponse, error) { func (UnimplementedWalletServiceServer) UpdateRechargeProduct(context.Context, *UpdateRechargeProductRequest) (*RechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRechargeProduct not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRechargeProduct not implemented")
} }
func (UnimplementedWalletServiceServer) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) { func (UnimplementedWalletServiceServer) DeleteRechargeProduct(context.Context, *DeleteRechargeProductRequest) (*DeleteRechargeProductResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteRechargeProduct not implemented") return nil, status.Error(codes.Unimplemented, "method DeleteRechargeProduct not implemented")
} }
func (UnimplementedWalletServiceServer) ListThirdPartyPaymentChannels(context.Context, *ListThirdPartyPaymentChannelsRequest) (*ListThirdPartyPaymentChannelsResponse, error) { func (UnimplementedWalletServiceServer) ListThirdPartyPaymentChannels(context.Context, *ListThirdPartyPaymentChannelsRequest) (*ListThirdPartyPaymentChannelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListThirdPartyPaymentChannels not implemented") return nil, status.Error(codes.Unimplemented, "method ListThirdPartyPaymentChannels not implemented")
} }
func (UnimplementedWalletServiceServer) SetThirdPartyPaymentMethodStatus(context.Context, *SetThirdPartyPaymentMethodStatusRequest) (*ThirdPartyPaymentMethodResponse, error) { func (UnimplementedWalletServiceServer) SetThirdPartyPaymentMethodStatus(context.Context, *SetThirdPartyPaymentMethodStatusRequest) (*ThirdPartyPaymentMethodResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetThirdPartyPaymentMethodStatus not implemented") return nil, status.Error(codes.Unimplemented, "method SetThirdPartyPaymentMethodStatus not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error) { func (UnimplementedWalletServiceServer) UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateThirdPartyPaymentRate not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateThirdPartyPaymentRate not implemented")
} }
func (UnimplementedWalletServiceServer) SyncThirdPartyPaymentMethods(context.Context, *SyncThirdPartyPaymentMethodsRequest) (*SyncThirdPartyPaymentMethodsResponse, error) { func (UnimplementedWalletServiceServer) SyncThirdPartyPaymentMethods(context.Context, *SyncThirdPartyPaymentMethodsRequest) (*SyncThirdPartyPaymentMethodsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SyncThirdPartyPaymentMethods not implemented") return nil, status.Error(codes.Unimplemented, "method SyncThirdPartyPaymentMethods not implemented")
} }
func (UnimplementedWalletServiceServer) ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error) { func (UnimplementedWalletServiceServer) ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListH5RechargeOptions not implemented") return nil, status.Error(codes.Unimplemented, "method ListH5RechargeOptions not implemented")
} }
func (UnimplementedWalletServiceServer) CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) { func (UnimplementedWalletServiceServer) CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateH5RechargeOrder not implemented") return nil, status.Error(codes.Unimplemented, "method CreateH5RechargeOrder not implemented")
} }
func (UnimplementedWalletServiceServer) CreateTemporaryRechargeOrder(context.Context, *CreateTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) { func (UnimplementedWalletServiceServer) CreateTemporaryRechargeOrder(context.Context, *CreateTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateTemporaryRechargeOrder not implemented") return nil, status.Error(codes.Unimplemented, "method CreateTemporaryRechargeOrder not implemented")
} }
func (UnimplementedWalletServiceServer) GetTemporaryRechargeOrder(context.Context, *GetTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) { func (UnimplementedWalletServiceServer) GetTemporaryRechargeOrder(context.Context, *GetTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTemporaryRechargeOrder not implemented") return nil, status.Error(codes.Unimplemented, "method GetTemporaryRechargeOrder not implemented")
} }
func (UnimplementedWalletServiceServer) ListTemporaryRechargeOrders(context.Context, *ListTemporaryRechargeOrdersRequest) (*ListTemporaryRechargeOrdersResponse, error) { func (UnimplementedWalletServiceServer) ListTemporaryRechargeOrders(context.Context, *ListTemporaryRechargeOrdersRequest) (*ListTemporaryRechargeOrdersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListTemporaryRechargeOrders not implemented") return nil, status.Error(codes.Unimplemented, "method ListTemporaryRechargeOrders not implemented")
} }
func (UnimplementedWalletServiceServer) SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error) { func (UnimplementedWalletServiceServer) SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SubmitH5RechargeTx not implemented") return nil, status.Error(codes.Unimplemented, "method SubmitH5RechargeTx not implemented")
} }
func (UnimplementedWalletServiceServer) GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) { func (UnimplementedWalletServiceServer) GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetH5RechargeOrder not implemented") return nil, status.Error(codes.Unimplemented, "method GetH5RechargeOrder not implemented")
} }
func (UnimplementedWalletServiceServer) VerifyCoinSellerRechargeReceipt(context.Context, *VerifyCoinSellerRechargeReceiptRequest) (*VerifyCoinSellerRechargeReceiptResponse, error) { func (UnimplementedWalletServiceServer) VerifyCoinSellerRechargeReceipt(context.Context, *VerifyCoinSellerRechargeReceiptRequest) (*VerifyCoinSellerRechargeReceiptResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method VerifyCoinSellerRechargeReceipt not implemented") return nil, status.Error(codes.Unimplemented, "method VerifyCoinSellerRechargeReceipt not implemented")
} }
func (UnimplementedWalletServiceServer) HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error) { func (UnimplementedWalletServiceServer) HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method HandleMifapayNotify not implemented") return nil, status.Error(codes.Unimplemented, "method HandleMifapayNotify not implemented")
} }
func (UnimplementedWalletServiceServer) HandleV5PayNotify(context.Context, *HandleV5PayNotifyRequest) (*HandleV5PayNotifyResponse, error) { func (UnimplementedWalletServiceServer) HandleV5PayNotify(context.Context, *HandleV5PayNotifyRequest) (*HandleV5PayNotifyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method HandleV5PayNotify not implemented") return nil, status.Error(codes.Unimplemented, "method HandleV5PayNotify not implemented")
} }
func (UnimplementedWalletServiceServer) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) { func (UnimplementedWalletServiceServer) GetDiamondExchangeConfig(context.Context, *GetDiamondExchangeConfigRequest) (*GetDiamondExchangeConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetDiamondExchangeConfig not implemented")
} }
func (UnimplementedWalletServiceServer) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) { func (UnimplementedWalletServiceServer) ListWalletTransactions(context.Context, *ListWalletTransactionsRequest) (*ListWalletTransactionsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWalletTransactions not implemented") return nil, status.Error(codes.Unimplemented, "method ListWalletTransactions not implemented")
} }
func (UnimplementedWalletServiceServer) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) { func (UnimplementedWalletServiceServer) ListVipPackages(context.Context, *ListVipPackagesRequest) (*ListVipPackagesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListVipPackages not implemented") return nil, status.Error(codes.Unimplemented, "method ListVipPackages not implemented")
} }
func (UnimplementedWalletServiceServer) GetVipProgramConfig(context.Context, *GetVipProgramConfigRequest) (*GetVipProgramConfigResponse, error) { func (UnimplementedWalletServiceServer) GetVipProgramConfig(context.Context, *GetVipProgramConfigRequest) (*GetVipProgramConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVipProgramConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetVipProgramConfig not implemented")
} }
func (UnimplementedWalletServiceServer) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) { func (UnimplementedWalletServiceServer) GetMyVip(context.Context, *GetMyVipRequest) (*GetMyVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyVip not implemented") return nil, status.Error(codes.Unimplemented, "method GetMyVip not implemented")
} }
func (UnimplementedWalletServiceServer) CheckVipBenefit(context.Context, *CheckVipBenefitRequest) (*CheckVipBenefitResponse, error) { func (UnimplementedWalletServiceServer) CheckVipBenefit(context.Context, *CheckVipBenefitRequest) (*CheckVipBenefitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckVipBenefit not implemented") return nil, status.Error(codes.Unimplemented, "method CheckVipBenefit not implemented")
} }
func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) { func (UnimplementedWalletServiceServer) PurchaseVip(context.Context, *PurchaseVipRequest) (*PurchaseVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PurchaseVip not implemented") return nil, status.Error(codes.Unimplemented, "method PurchaseVip not implemented")
} }
func (UnimplementedWalletServiceServer) DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error) { func (UnimplementedWalletServiceServer) DebitCPBreakupFee(context.Context, *DebitCPBreakupFeeRequest) (*DebitCPBreakupFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitCPBreakupFee not implemented") return nil, status.Error(codes.Unimplemented, "method DebitCPBreakupFee not implemented")
} }
func (UnimplementedWalletServiceServer) DebitWheelDraw(context.Context, *DebitWheelDrawRequest) (*DebitWheelDrawResponse, error) { func (UnimplementedWalletServiceServer) DebitWheelDraw(context.Context, *DebitWheelDrawRequest) (*DebitWheelDrawResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebitWheelDraw not implemented") return nil, status.Error(codes.Unimplemented, "method DebitWheelDraw not implemented")
} }
func (UnimplementedWalletServiceServer) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) { func (UnimplementedWalletServiceServer) GrantVip(context.Context, *GrantVipRequest) (*GrantVipResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantVip not implemented") return nil, status.Error(codes.Unimplemented, "method GrantVip not implemented")
} }
func (UnimplementedWalletServiceServer) GrantVipTrialCard(context.Context, *GrantVipTrialCardRequest) (*GrantVipTrialCardResponse, error) { func (UnimplementedWalletServiceServer) GrantVipTrialCard(context.Context, *GrantVipTrialCardRequest) (*GrantVipTrialCardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GrantVipTrialCard not implemented") return nil, status.Error(codes.Unimplemented, "method GrantVipTrialCard not implemented")
} }
func (UnimplementedWalletServiceServer) EquipVipTrialCard(context.Context, *EquipVipTrialCardRequest) (*EquipVipTrialCardResponse, error) { func (UnimplementedWalletServiceServer) EquipVipTrialCard(context.Context, *EquipVipTrialCardRequest) (*EquipVipTrialCardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EquipVipTrialCard not implemented") return nil, status.Error(codes.Unimplemented, "method EquipVipTrialCard not implemented")
} }
func (UnimplementedWalletServiceServer) UnequipVipTrialCard(context.Context, *UnequipVipTrialCardRequest) (*UnequipVipTrialCardResponse, error) { func (UnimplementedWalletServiceServer) UnequipVipTrialCard(context.Context, *UnequipVipTrialCardRequest) (*UnequipVipTrialCardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnequipVipTrialCard not implemented") return nil, status.Error(codes.Unimplemented, "method UnequipVipTrialCard not implemented")
} }
func (UnimplementedWalletServiceServer) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) { func (UnimplementedWalletServiceServer) ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAdminVipLevels not implemented") return nil, status.Error(codes.Unimplemented, "method ListAdminVipLevels not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) { func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipLevels not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateAdminVipLevels not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateAdminVipProgramConfig(context.Context, *UpdateAdminVipProgramConfigRequest) (*UpdateAdminVipProgramConfigResponse, error) { func (UnimplementedWalletServiceServer) UpdateAdminVipProgramConfig(context.Context, *UpdateAdminVipProgramConfigRequest) (*UpdateAdminVipProgramConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateAdminVipProgramConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateAdminVipProgramConfig not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateMyVipSettings(context.Context, *UpdateMyVipSettingsRequest) (*UpdateMyVipSettingsResponse, error) { func (UnimplementedWalletServiceServer) UpdateMyVipSettings(context.Context, *UpdateMyVipSettingsRequest) (*UpdateMyVipSettingsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateMyVipSettings not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateMyVipSettings not implemented")
} }
func (UnimplementedWalletServiceServer) GetMyCurrentVipDailyCoinRebate(context.Context, *GetMyCurrentVipDailyCoinRebateRequest) (*GetMyCurrentVipDailyCoinRebateResponse, error) { func (UnimplementedWalletServiceServer) GetMyCurrentVipDailyCoinRebate(context.Context, *GetMyCurrentVipDailyCoinRebateRequest) (*GetMyCurrentVipDailyCoinRebateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMyCurrentVipDailyCoinRebate not implemented") return nil, status.Error(codes.Unimplemented, "method GetMyCurrentVipDailyCoinRebate not implemented")
} }
func (UnimplementedWalletServiceServer) ListMyVipDailyCoinRebateStatuses(context.Context, *ListMyVipDailyCoinRebateStatusesRequest) (*ListMyVipDailyCoinRebateStatusesResponse, error) { func (UnimplementedWalletServiceServer) ListMyVipDailyCoinRebateStatuses(context.Context, *ListMyVipDailyCoinRebateStatusesRequest) (*ListMyVipDailyCoinRebateStatusesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListMyVipDailyCoinRebateStatuses not implemented") return nil, status.Error(codes.Unimplemented, "method ListMyVipDailyCoinRebateStatuses not implemented")
} }
func (UnimplementedWalletServiceServer) ClaimVipDailyCoinRebate(context.Context, *ClaimVipDailyCoinRebateRequest) (*ClaimVipDailyCoinRebateResponse, error) { func (UnimplementedWalletServiceServer) ClaimVipDailyCoinRebate(context.Context, *ClaimVipDailyCoinRebateRequest) (*ClaimVipDailyCoinRebateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimVipDailyCoinRebate not implemented") return nil, status.Error(codes.Unimplemented, "method ClaimVipDailyCoinRebate not implemented")
} }
func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditTaskReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditTaskReward not implemented")
} }
func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditLuckyGiftReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditLuckyGiftReward not implemented")
} }
func (UnimplementedWalletServiceServer) CreditWheelReward(context.Context, *CreditWheelRewardRequest) (*CreditWheelRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditWheelReward(context.Context, *CreditWheelRewardRequest) (*CreditWheelRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditWheelReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditWheelReward not implemented")
} }
func (UnimplementedWalletServiceServer) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditRoomTurnoverReward(context.Context, *CreditRoomTurnoverRewardRequest) (*CreditRoomTurnoverRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditRoomTurnoverReward not implemented")
} }
func (UnimplementedWalletServiceServer) CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditInviteActivityReward(context.Context, *CreditInviteActivityRewardRequest) (*CreditInviteActivityRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditInviteActivityReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditInviteActivityReward not implemented")
} }
func (UnimplementedWalletServiceServer) CreditAgencyOpeningReward(context.Context, *CreditAgencyOpeningRewardRequest) (*CreditAgencyOpeningRewardResponse, error) { func (UnimplementedWalletServiceServer) CreditAgencyOpeningReward(context.Context, *CreditAgencyOpeningRewardRequest) (*CreditAgencyOpeningRewardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreditAgencyOpeningReward not implemented") return nil, status.Error(codes.Unimplemented, "method CreditAgencyOpeningReward not implemented")
} }
func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) { func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApplyGameCoinChange not implemented") return nil, status.Error(codes.Unimplemented, "method ApplyGameCoinChange not implemented")
} }
func (UnimplementedWalletServiceServer) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) { func (UnimplementedWalletServiceServer) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacketConfig not implemented") return nil, status.Error(codes.Unimplemented, "method GetRedPacketConfig not implemented")
} }
func (UnimplementedWalletServiceServer) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) { func (UnimplementedWalletServiceServer) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRedPacketConfig not implemented") return nil, status.Error(codes.Unimplemented, "method UpdateRedPacketConfig not implemented")
} }
func (UnimplementedWalletServiceServer) CreateRedPacket(context.Context, *CreateRedPacketRequest) (*CreateRedPacketResponse, error) { func (UnimplementedWalletServiceServer) CreateRedPacket(context.Context, *CreateRedPacketRequest) (*CreateRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateRedPacket not implemented") return nil, status.Error(codes.Unimplemented, "method CreateRedPacket not implemented")
} }
func (UnimplementedWalletServiceServer) ClaimRedPacket(context.Context, *ClaimRedPacketRequest) (*ClaimRedPacketResponse, error) { func (UnimplementedWalletServiceServer) ClaimRedPacket(context.Context, *ClaimRedPacketRequest) (*ClaimRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimRedPacket not implemented") return nil, status.Error(codes.Unimplemented, "method ClaimRedPacket not implemented")
} }
func (UnimplementedWalletServiceServer) ListRedPackets(context.Context, *ListRedPacketsRequest) (*ListRedPacketsResponse, error) { func (UnimplementedWalletServiceServer) ListRedPackets(context.Context, *ListRedPacketsRequest) (*ListRedPacketsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRedPackets not implemented") return nil, status.Error(codes.Unimplemented, "method ListRedPackets not implemented")
} }
func (UnimplementedWalletServiceServer) GetRedPacket(context.Context, *GetRedPacketRequest) (*GetRedPacketResponse, error) { func (UnimplementedWalletServiceServer) GetRedPacket(context.Context, *GetRedPacketRequest) (*GetRedPacketResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRedPacket not implemented") return nil, status.Error(codes.Unimplemented, "method GetRedPacket not implemented")
} }
func (UnimplementedWalletServiceServer) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) { func (UnimplementedWalletServiceServer) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExpireRedPackets not implemented") return nil, status.Error(codes.Unimplemented, "method ExpireRedPackets not implemented")
} }
func (UnimplementedWalletServiceServer) RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) { func (UnimplementedWalletServiceServer) RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RetryRedPacketRefund not implemented") return nil, status.Error(codes.Unimplemented, "method RetryRedPacketRefund not implemented")
} }
func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {} func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {}
func (UnimplementedWalletServiceServer) testEmbeddedByValue() {} func (UnimplementedWalletServiceServer) testEmbeddedByValue() {}
@ -2270,7 +2302,7 @@ type UnsafeWalletServiceServer interface {
} }
func RegisterWalletServiceServer(s grpc.ServiceRegistrar, srv WalletServiceServer) { func RegisterWalletServiceServer(s grpc.ServiceRegistrar, srv WalletServiceServer) {
// If the following call pancis, it indicates UnimplementedWalletServiceServer was // If the following call panics, it indicates UnimplementedWalletServiceServer was
// embedded by pointer and is nil. This will cause panics if an // embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization // unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O. // time to prevent it from happening at runtime later due to I/O.
@ -2982,6 +3014,24 @@ func _WalletService_SetResourceGroupStatus_Handler(srv interface{}, ctx context.
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletService_PinResourceGroupSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PinResourceGroupSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).PinResourceGroupSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_PinResourceGroupSnapshot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).PinResourceGroupSnapshot(ctx, req.(*PinResourceGroupSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_ListGiftConfigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletService_ListGiftConfigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListGiftConfigsRequest) in := new(ListGiftConfigsRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -3180,6 +3230,24 @@ func _WalletService_GrantResourceGroup_Handler(srv interface{}, ctx context.Cont
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletService_GrantPinnedResourceGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GrantPinnedResourceGroupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).GrantPinnedResourceGroup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_GrantPinnedResourceGroup_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).GrantPinnedResourceGroup(ctx, req.(*GrantPinnedResourceGroupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_RevokeResourceGrant_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletService_RevokeResourceGrant_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RevokeResourceGrantRequest) in := new(RevokeResourceGrantRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -4675,6 +4743,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetResourceGroupStatus", MethodName: "SetResourceGroupStatus",
Handler: _WalletService_SetResourceGroupStatus_Handler, Handler: _WalletService_SetResourceGroupStatus_Handler,
}, },
{
MethodName: "PinResourceGroupSnapshot",
Handler: _WalletService_PinResourceGroupSnapshot_Handler,
},
{ {
MethodName: "ListGiftConfigs", MethodName: "ListGiftConfigs",
Handler: _WalletService_ListGiftConfigs_Handler, Handler: _WalletService_ListGiftConfigs_Handler,
@ -4719,6 +4791,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GrantResourceGroup", MethodName: "GrantResourceGroup",
Handler: _WalletService_GrantResourceGroup_Handler, Handler: _WalletService_GrantResourceGroup_Handler,
}, },
{
MethodName: "GrantPinnedResourceGroup",
Handler: _WalletService_GrantPinnedResourceGroup_Handler,
},
{ {
MethodName: "RevokeResourceGrant", MethodName: "RevokeResourceGrant",
Handler: _WalletService_RevokeResourceGrant_Handler, Handler: _WalletService_RevokeResourceGrant_Handler,

View File

@ -99,8 +99,6 @@ services:
container_name: wallet-service container_name: wallet-service
environment: environment:
TZ: UTC TZ: UTC
volumes:
- ./.env:/app/.env:ro
ports: ports:
- "13004:13004" - "13004:13004"
- "13104:13104" - "13104:13104"

181
pkg/activitymq/messages.go Normal file
View File

@ -0,0 +1,181 @@
// Package activitymq defines the RocketMQ fact contract owned by activity-service.
package activitymq
import (
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"time"
)
const (
MessageTypeActivityTemplateFact = "activity_template_fact"
TagActivityTemplateFact = MessageTypeActivityTemplateFact
EventTypeGiftScored = "GiftScored"
EventTypeTaskProgressed = "TaskProgressed"
EventTypeTaskClaimed = "TaskClaimed"
EventTypeActivityVisited = "ActivityVisited"
)
// ActivityTemplateFactMessage is a self-contained immutable activity fact. Every
// dimension needed by statistics is carried in the message so consumers never
// need to read activity-service owner tables.
type ActivityTemplateFactMessage struct {
MessageType string `json:"message_type"`
AppCode string `json:"app_code"`
EventID string `json:"event_id"`
EventType string `json:"event_type"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
PublishedVersion int64 `json:"published_version"`
StatDay string `json:"stat_day"`
RegionID int64 `json:"region_id"`
UserID int64 `json:"user_id"`
GiftID string `json:"gift_id"`
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
GiftCountDelta int64 `json:"gift_count_delta"`
CoinAmountDelta int64 `json:"coin_amount_delta"`
ProgressDelta int64 `json:"progress_delta"`
CompletedDelta int64 `json:"completed_delta"`
ClaimedDelta int64 `json:"claimed_delta"`
VisitCountDelta int64 `json:"visit_count_delta"`
OccurredAtMS int64 `json:"occurred_at_ms"`
AttributionAtMS int64 `json:"attribution_at_ms"`
}
// EventTypeTag accepts only committed owner facts. Keeping the allow-list here
// prevents a malformed database value from becoming a broker routing expression.
func EventTypeTag(eventType string) (string, error) {
switch tag := strings.TrimSpace(eventType); tag {
case EventTypeGiftScored, EventTypeTaskProgressed, EventTypeTaskClaimed, EventTypeActivityVisited:
return tag, nil
case "":
return "", errors.New("activity event_type tag is required")
default:
return "", fmt.Errorf("unsupported activity event_type tag %q", eventType)
}
}
// LegacyCompatibleTagExpression keeps messages published before typed tags were
// enabled consumable while making all new deliveries broker-filtered and explicit.
func LegacyCompatibleTagExpression(eventTypes ...string) (string, error) {
tags := make([]string, 0, len(eventTypes))
seen := make(map[string]struct{}, len(eventTypes))
for _, eventType := range eventTypes {
tag, err := EventTypeTag(eventType)
if err != nil {
return "", err
}
if _, exists := seen[tag]; exists {
continue
}
seen[tag] = struct{}{}
tags = append(tags, tag)
}
if len(tags) == 0 {
return "", errors.New("at least one typed activity event tag is required")
}
sort.Strings(tags)
return TagActivityTemplateFact + " || " + strings.Join(tags, " || "), nil
}
// EncodeActivityTemplateFactMessage normalizes the wrapper discriminator and
// rejects poison facts before they can leave the transactional outbox relay.
func EncodeActivityTemplateFactMessage(message ActivityTemplateFactMessage) ([]byte, error) {
message.MessageType = MessageTypeActivityTemplateFact
message = normalizeActivityTemplateFact(message)
if err := validateActivityTemplateFact(message); err != nil {
return nil, err
}
return json.Marshal(message)
}
// DecodeActivityTemplateFactMessage applies the same validation at every ingest
// boundary, including replay tooling that may bypass the original producer.
func DecodeActivityTemplateFactMessage(body []byte) (ActivityTemplateFactMessage, error) {
var message ActivityTemplateFactMessage
if err := json.Unmarshal(body, &message); err != nil {
return ActivityTemplateFactMessage{}, err
}
if message.MessageType != MessageTypeActivityTemplateFact {
return ActivityTemplateFactMessage{}, errors.New("unexpected activity template fact message_type")
}
message = normalizeActivityTemplateFact(message)
if err := validateActivityTemplateFact(message); err != nil {
return ActivityTemplateFactMessage{}, err
}
return message, nil
}
func normalizeActivityTemplateFact(message ActivityTemplateFactMessage) ActivityTemplateFactMessage {
message.AppCode = strings.TrimSpace(message.AppCode)
message.EventID = strings.TrimSpace(message.EventID)
message.EventType = strings.TrimSpace(message.EventType)
message.TemplateID = strings.TrimSpace(message.TemplateID)
message.TemplateCode = strings.TrimSpace(message.TemplateCode)
message.StatDay = strings.TrimSpace(message.StatDay)
message.GiftID = strings.TrimSpace(message.GiftID)
message.TaskKey = strings.TrimSpace(message.TaskKey)
message.TaskType = strings.TrimSpace(message.TaskType)
if message.AttributionAtMS <= 0 {
message.AttributionAtMS = message.OccurredAtMS
}
return message
}
func validateActivityTemplateFact(message ActivityTemplateFactMessage) error {
if message.AppCode == "" ||
message.EventID == "" ||
message.TemplateID == "" ||
message.TemplateCode == "" ||
message.PublishedVersion <= 0 ||
message.RegionID < 0 ||
message.UserID <= 0 ||
message.OccurredAtMS <= 0 {
return errors.New("activity template fact is incomplete")
}
if _, err := EventTypeTag(message.EventType); err != nil {
return err
}
if message.GiftCountDelta < 0 || message.CoinAmountDelta < 0 ||
message.ProgressDelta < 0 || message.CompletedDelta < 0 || message.ClaimedDelta < 0 || message.VisitCountDelta < 0 {
return errors.New("activity template fact deltas cannot be negative")
}
parsedDay, err := time.Parse("2006-01-02", message.StatDay)
if err != nil || parsedDay.Format("2006-01-02") != message.StatDay {
return errors.New("activity template fact stat_day must be a UTC YYYY-MM-DD date")
}
// Task claims can be executed days after completion. stat_day remains the task earning day while
// occurred_at_ms is the truthful wallet-grant time; attribution_at_ms proves the owner-selected bucket.
daySourceMS := message.OccurredAtMS
if message.EventType == EventTypeTaskClaimed {
daySourceMS = message.AttributionAtMS
}
if time.UnixMilli(daySourceMS).UTC().Format("2006-01-02") != message.StatDay {
return errors.New("activity template fact stat_day does not match attribution time")
}
switch message.EventType {
case EventTypeGiftScored:
if message.GiftID == "" || message.GiftCountDelta <= 0 {
return errors.New("GiftScored requires gift_id and positive gift_count_delta")
}
case EventTypeTaskProgressed:
if message.TaskKey == "" || message.TaskType == "" ||
(message.ProgressDelta == 0 && message.CompletedDelta == 0) {
return errors.New("TaskProgressed requires task identity and a progress or completed delta")
}
case EventTypeTaskClaimed:
if message.TaskKey == "" || message.TaskType == "" || message.ClaimedDelta <= 0 {
return errors.New("TaskClaimed requires task identity and positive claimed_delta")
}
case EventTypeActivityVisited:
if message.VisitCountDelta <= 0 {
return errors.New("ActivityVisited requires positive visit_count_delta")
}
}
return nil
}

View File

@ -0,0 +1,104 @@
package activitymq
import (
"strings"
"testing"
"time"
)
func TestActivityTemplateFactRoundTrip(t *testing.T) {
occurredAt := time.Date(2026, time.July, 14, 8, 30, 0, 0, time.UTC).UnixMilli()
want := ActivityTemplateFactMessage{
AppCode: "lalu", EventID: "activity:g1", EventType: EventTypeGiftScored,
TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
StatDay: "2026-07-14", RegionID: 9, UserID: 1001, GiftID: "rose",
GiftCountDelta: 2, CoinAmountDelta: 200, OccurredAtMS: occurredAt,
}
body, err := EncodeActivityTemplateFactMessage(want)
if err != nil {
t.Fatalf("encode: %v", err)
}
got, err := DecodeActivityTemplateFactMessage(body)
if err != nil {
t.Fatalf("decode: %v", err)
}
if got.MessageType != MessageTypeActivityTemplateFact || got.EventID != want.EventID || got.CoinAmountDelta != 200 {
t.Fatalf("unexpected round trip: %+v", got)
}
}
func TestActivityTemplateFactRejectsMismatchedDayAndIncompleteDimensions(t *testing.T) {
base := ActivityTemplateFactMessage{
AppCode: "lalu", EventID: "activity:t1", EventType: EventTypeTaskProgressed,
TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 1,
StatDay: "2026-07-13", UserID: 1001, TaskKey: "send_gift", TaskType: "gift",
ProgressDelta: 1, OccurredAtMS: time.Date(2026, time.July, 14, 0, 0, 0, 0, time.UTC).UnixMilli(),
}
if _, err := EncodeActivityTemplateFactMessage(base); err == nil || !strings.Contains(err.Error(), "does not match") {
t.Fatalf("expected stat day mismatch, got %v", err)
}
base.StatDay = "2026-07-14"
base.TaskKey = ""
if _, err := EncodeActivityTemplateFactMessage(base); err == nil {
t.Fatal("expected incomplete task identity rejection")
}
}
func TestTaskClaimedUsesImmutableEarningAttributionDay(t *testing.T) {
earningAt := time.Date(2026, time.July, 14, 23, 50, 0, 0, time.UTC).UnixMilli()
claimedAt := time.Date(2026, time.July, 17, 9, 15, 0, 0, time.UTC).UnixMilli()
want := ActivityTemplateFactMessage{
AppCode: "lalu", EventID: "activity:claim:delayed", EventType: EventTypeTaskClaimed,
TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
StatDay: "2026-07-14", RegionID: 9, UserID: 1001, TaskKey: "send_gift", TaskType: "gift",
ClaimedDelta: 1, OccurredAtMS: claimedAt, AttributionAtMS: earningAt,
}
body, err := EncodeActivityTemplateFactMessage(want)
if err != nil {
t.Fatalf("encode delayed task claim: %v", err)
}
got, err := DecodeActivityTemplateFactMessage(body)
if err != nil {
t.Fatalf("decode delayed task claim: %v", err)
}
if got.StatDay != "2026-07-14" || got.OccurredAtMS != claimedAt || got.AttributionAtMS != earningAt {
t.Fatalf("task claim attribution changed: %+v", got)
}
// A claim cannot relabel its earning day independently from the persisted
// attribution timestamp, otherwise retry time or a moved user region could
// rewrite already-earned statistics.
want.AttributionAtMS = time.Date(2026, time.July, 15, 0, 0, 0, 0, time.UTC).UnixMilli()
if _, err := EncodeActivityTemplateFactMessage(want); err == nil || !strings.Contains(err.Error(), "attribution") {
t.Fatalf("expected attribution mismatch rejection, got %v", err)
}
}
func TestActivityVisitedRequiresPositiveVisitDelta(t *testing.T) {
visitedAt := time.Date(2026, time.July, 14, 8, 30, 0, 0, time.UTC).UnixMilli()
fact := ActivityTemplateFactMessage{
AppCode: "lalu", EventID: "activity:visit:1", EventType: EventTypeActivityVisited,
TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
StatDay: "2026-07-14", RegionID: 9, UserID: 1001, OccurredAtMS: visitedAt,
}
if _, err := EncodeActivityTemplateFactMessage(fact); err == nil {
t.Fatal("expected zero visit delta rejection")
}
fact.VisitCountDelta = 1
if _, err := EncodeActivityTemplateFactMessage(fact); err != nil {
t.Fatalf("encode activity visit: %v", err)
}
}
func TestActivityTemplateFactTagExpressionIsSafeAndDeterministic(t *testing.T) {
expression, err := LegacyCompatibleTagExpression(EventTypeTaskClaimed, EventTypeGiftScored, EventTypeTaskClaimed)
if err != nil {
t.Fatalf("expression: %v", err)
}
if expression != "activity_template_fact || GiftScored || TaskClaimed" {
t.Fatalf("unexpected expression: %s", expression)
}
if _, err := EventTypeTag("GiftScored || *"); err == nil {
t.Fatal("expected unsafe tag rejection")
}
}

View File

@ -48,6 +48,7 @@ TOPICS=(
"hyapp_room_rocket_launch" "hyapp_room_rocket_launch"
"hyapp_user_outbox" "hyapp_user_outbox"
"hyapp_message_action_outbox" "hyapp_message_action_outbox"
"hyapp_activity_outbox"
"hyapp_game_outbox" "hyapp_game_outbox"
) )

View File

@ -20,6 +20,29 @@ OPTIONAL_SQL_FILES=(
mkdir -p "${LOCAL_INITDB_DIR}" mkdir -p "${LOCAL_INITDB_DIR}"
append_current_repo_grants() {
local target_file="$1"
cat >> "${target_file}" <<'EOF'
-- Local bootstrap follows the databases owned by this repository. The optional
-- deploy-platform grants can lag a newly added service or be absent in a lightweight
-- checkout; every current owner must still be able to start with the shared local user.
CREATE USER IF NOT EXISTS 'hyapp'@'%' IDENTIFIED BY 'hyapp';
GRANT ALL PRIVILEGES ON hyapp_room.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_user.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_wallet.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_activity.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_lucky_gift.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_cron.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_game.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_robot.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_notice.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_statistics.* TO 'hyapp'@'%';
GRANT ALL PRIVILEGES ON hyapp_admin.* TO 'hyapp'@'%';
FLUSH PRIVILEGES;
EOF
}
for sql_name in "${OPTIONAL_SQL_FILES[@]}"; do for sql_name in "${OPTIONAL_SQL_FILES[@]}"; do
source_file="${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/${sql_name}" source_file="${DEPLOY_PLATFORM_ROOT}/deploy/mysql/initdb/${sql_name}"
target_file="${LOCAL_INITDB_DIR}/${sql_name}" target_file="${LOCAL_INITDB_DIR}/${sql_name}"
@ -27,24 +50,22 @@ for sql_name in "${OPTIONAL_SQL_FILES[@]}"; do
if [[ -f "${source_file}" ]]; then if [[ -f "${source_file}" ]]; then
cp "${source_file}" "${target_file}" cp "${source_file}" "${target_file}"
if [[ "${sql_name}" == "999_local_grants.sql" ]]; then if [[ "${sql_name}" == "999_local_grants.sql" ]]; then
# hyapp-server 新增 owner 库时deploy-platform 的本地授权文件可能还没同步。 append_current_repo_grants "${target_file}"
# 本地启动必须以当前仓库服务清单为准,否则新服务库已经创建但 hyapp 用户无法连接。
cat >> "${target_file}" <<'EOF'
GRANT ALL PRIVILEGES ON hyapp_lucky_gift.* TO 'hyapp'@'%';
FLUSH PRIVILEGES;
EOF
fi fi
printf 'prepared optional mysql init file: %s\n' "${source_file}" printf 'prepared optional mysql init file: %s\n' "${source_file}"
continue continue
fi fi
printf -- '-- optional mysql init file is absent locally: %s\n' "${source_file}" > "${target_file}" printf -- '-- optional mysql init file is absent locally: %s\n' "${source_file}" > "${target_file}"
if [[ "${sql_name}" == "999_local_grants.sql" ]]; then if [[ "${sql_name}" == "006_admin_database.sql" ]]; then
# server/admin owns its migrations but requires the database itself before the
# process can connect and run them. Keep the fallback minimal and idempotent.
cat >> "${target_file}" <<'EOF' cat >> "${target_file}" <<'EOF'
CREATE USER IF NOT EXISTS 'hyapp'@'%' IDENTIFIED BY 'hyapp'; CREATE DATABASE IF NOT EXISTS hyapp_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON hyapp_lucky_gift.* TO 'hyapp'@'%';
FLUSH PRIVILEGES;
EOF EOF
fi fi
if [[ "${sql_name}" == "999_local_grants.sql" ]]; then
append_current_repo_grants "${target_file}"
fi
printf 'prepared empty optional mysql init file: %s\n' "${source_file}" printf 'prepared empty optional mysql init file: %s\n' "${source_file}"
done done

View File

@ -23,11 +23,13 @@ import (
"hyapp-admin-server/internal/integration/luckygiftadmin" "hyapp-admin-server/internal/integration/luckygiftadmin"
"hyapp-admin-server/internal/integration/robotclient" "hyapp-admin-server/internal/integration/robotclient"
"hyapp-admin-server/internal/integration/roomclient" "hyapp-admin-server/internal/integration/roomclient"
"hyapp-admin-server/internal/integration/statisticsclient"
"hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/integration/walletclient" "hyapp-admin-server/internal/integration/walletclient"
jobrunner "hyapp-admin-server/internal/job" jobrunner "hyapp-admin-server/internal/job"
"hyapp-admin-server/internal/migration" "hyapp-admin-server/internal/migration"
achievementconfigmodule "hyapp-admin-server/internal/modules/achievementconfig" achievementconfigmodule "hyapp-admin-server/internal/modules/achievementconfig"
activitytemplatemodule "hyapp-admin-server/internal/modules/activitytemplate"
adminusermodule "hyapp-admin-server/internal/modules/adminuser" adminusermodule "hyapp-admin-server/internal/modules/adminuser"
agencyopeningmodule "hyapp-admin-server/internal/modules/agencyopening" agencyopeningmodule "hyapp-admin-server/internal/modules/agencyopening"
appconfigmodule "hyapp-admin-server/internal/modules/appconfig" appconfigmodule "hyapp-admin-server/internal/modules/appconfig"
@ -355,6 +357,10 @@ func main() {
Auth: authmodule.New(store, auth, auditHandler, cfg), Auth: authmodule.New(store, auth, auditHandler, cfg),
AdminUser: adminusermodule.New(store, cfg, auditHandler), AdminUser: adminusermodule.New(store, cfg, auditHandler),
AgencyOpening: agencyopeningmodule.New(activityclient.NewGRPC(activityConn), auditHandler), AgencyOpening: agencyopeningmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
ActivityTemplate: activitytemplatemodule.New(
activityclient.NewGRPC(activityConn), auditHandler,
statisticsclient.NewHTTP(cfg.StatisticsService.BaseURL, cfg.StatisticsService.RequestTimeout),
).BindUserBatchGetter(userclient.NewGRPC(userConn)),
AchievementConfig: achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler), AchievementConfig: achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
AppConfig: appconfigmodule.New(store, auditHandler), AppConfig: appconfigmodule.New(store, auditHandler),
AppRegistry: appregistrymodule.New(userDB, store), AppRegistry: appregistrymodule.New(userDB, store),

View File

@ -8,7 +8,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
// Client 是 admin-server 访问 activity-service 任务配置能力的最小 gRPC 依赖 // Client 是 admin-server 访问 activity-service 后台配置能力的 gRPC 边界
type Client interface { type Client interface {
ListTaskDefinitions(ctx context.Context, req *activityv1.ListTaskDefinitionsRequest) (*activityv1.ListTaskDefinitionsResponse, error) ListTaskDefinitions(ctx context.Context, req *activityv1.ListTaskDefinitionsRequest) (*activityv1.ListTaskDefinitionsResponse, error)
UpsertTaskDefinition(ctx context.Context, req *activityv1.UpsertTaskDefinitionRequest) (*activityv1.UpsertTaskDefinitionResponse, error) UpsertTaskDefinition(ctx context.Context, req *activityv1.UpsertTaskDefinitionRequest) (*activityv1.UpsertTaskDefinitionResponse, error)
@ -51,6 +51,20 @@ type Client interface {
SetAgencyOpeningCycleStatus(ctx context.Context, req *activityv1.SetAgencyOpeningCycleStatusRequest) (*activityv1.SetAgencyOpeningCycleStatusResponse, error) SetAgencyOpeningCycleStatus(ctx context.Context, req *activityv1.SetAgencyOpeningCycleStatusRequest) (*activityv1.SetAgencyOpeningCycleStatusResponse, error)
ListAgencyOpeningApplications(ctx context.Context, req *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error) ListAgencyOpeningApplications(ctx context.Context, req *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error)
ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error) ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error)
ListActivityTemplates(ctx context.Context, req *activityv1.ListActivityTemplatesRequest) (*activityv1.ListActivityTemplatesResponse, error)
CreateActivityTemplate(ctx context.Context, req *activityv1.CreateActivityTemplateRequest) (*activityv1.CreateActivityTemplateResponse, error)
GetActivityTemplate(ctx context.Context, req *activityv1.GetActivityTemplateRequest) (*activityv1.GetActivityTemplateResponse, error)
UpdateActivityTemplate(ctx context.Context, req *activityv1.UpdateActivityTemplateRequest) (*activityv1.UpdateActivityTemplateResponse, error)
SetActivityTemplateStatus(ctx context.Context, req *activityv1.SetActivityTemplateStatusRequest) (*activityv1.SetActivityTemplateStatusResponse, error)
DeleteActivityTemplate(ctx context.Context, req *activityv1.DeleteActivityTemplateRequest) (*activityv1.DeleteActivityTemplateResponse, error)
ListActivityTemplateVersions(ctx context.Context, req *activityv1.ListActivityTemplateVersionsRequest) (*activityv1.ListActivityTemplateVersionsResponse, error)
CloneActivityTemplate(ctx context.Context, req *activityv1.CloneActivityTemplateRequest) (*activityv1.CloneActivityTemplateResponse, error)
ValidateActivityTemplate(ctx context.Context, req *activityv1.ValidateActivityTemplateRequest) (*activityv1.ValidateActivityTemplateResponse, error)
ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.AdminListActivityTemplateLeaderboardRequest) (*activityv1.AdminListActivityTemplateLeaderboardResponse, error)
ListActivityTemplateRewardJobs(ctx context.Context, req *activityv1.ListActivityTemplateRewardJobsRequest) (*activityv1.ListActivityTemplateRewardJobsResponse, error)
RetryActivityTemplateRewardJob(ctx context.Context, req *activityv1.RetryActivityTemplateRewardJobRequest) (*activityv1.RetryActivityTemplateRewardJobResponse, error)
ListActivityTemplateTaskClaims(ctx context.Context, req *activityv1.ListActivityTemplateTaskClaimsRequest) (*activityv1.ListActivityTemplateTaskClaimsResponse, error)
RetryActivityTemplateTaskClaim(ctx context.Context, req *activityv1.RetryActivityTemplateTaskClaimRequest) (*activityv1.RetryActivityTemplateTaskClaimResponse, error)
GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error) GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error)
UpdateCPWeeklyRankConfig(ctx context.Context, req *activityv1.UpdateCPWeeklyRankConfigRequest) (*activityv1.UpdateCPWeeklyRankConfigResponse, error) UpdateCPWeeklyRankConfig(ctx context.Context, req *activityv1.UpdateCPWeeklyRankConfigRequest) (*activityv1.UpdateCPWeeklyRankConfigResponse, error)
ListCPWeeklyRankSettlements(ctx context.Context, req *activityv1.ListCPWeeklyRankSettlementsRequest) (*activityv1.ListCPWeeklyRankSettlementsResponse, error) ListCPWeeklyRankSettlements(ctx context.Context, req *activityv1.ListCPWeeklyRankSettlementsRequest) (*activityv1.ListCPWeeklyRankSettlementsResponse, error)
@ -80,6 +94,7 @@ type GRPCClient struct {
roomTurnoverRewardClient activityv1.AdminRoomTurnoverRewardServiceClient roomTurnoverRewardClient activityv1.AdminRoomTurnoverRewardServiceClient
weeklyStarClient activityv1.AdminWeeklyStarServiceClient weeklyStarClient activityv1.AdminWeeklyStarServiceClient
agencyOpeningClient activityv1.AdminAgencyOpeningServiceClient agencyOpeningClient activityv1.AdminAgencyOpeningServiceClient
activityTemplateClient activityv1.AdminActivityTemplateServiceClient
cpWeeklyRankClient activityv1.AdminCPWeeklyRankServiceClient cpWeeklyRankClient activityv1.AdminCPWeeklyRankServiceClient
wheelClient activityv1.AdminWheelServiceClient wheelClient activityv1.AdminWheelServiceClient
broadcastClient activityv1.BroadcastServiceClient broadcastClient activityv1.BroadcastServiceClient
@ -100,6 +115,7 @@ func NewGRPC(conn grpc.ClientConnInterface) *GRPCClient {
roomTurnoverRewardClient: activityv1.NewAdminRoomTurnoverRewardServiceClient(conn), roomTurnoverRewardClient: activityv1.NewAdminRoomTurnoverRewardServiceClient(conn),
weeklyStarClient: activityv1.NewAdminWeeklyStarServiceClient(conn), weeklyStarClient: activityv1.NewAdminWeeklyStarServiceClient(conn),
agencyOpeningClient: activityv1.NewAdminAgencyOpeningServiceClient(conn), agencyOpeningClient: activityv1.NewAdminAgencyOpeningServiceClient(conn),
activityTemplateClient: activityv1.NewAdminActivityTemplateServiceClient(conn),
cpWeeklyRankClient: activityv1.NewAdminCPWeeklyRankServiceClient(conn), cpWeeklyRankClient: activityv1.NewAdminCPWeeklyRankServiceClient(conn),
wheelClient: activityv1.NewAdminWheelServiceClient(conn), wheelClient: activityv1.NewAdminWheelServiceClient(conn),
broadcastClient: activityv1.NewBroadcastServiceClient(conn), broadcastClient: activityv1.NewBroadcastServiceClient(conn),
@ -273,6 +289,62 @@ func (c *GRPCClient) ReviewAgencyOpeningApplication(ctx context.Context, req *ac
return c.agencyOpeningClient.ReviewAgencyOpeningApplication(ctx, req) return c.agencyOpeningClient.ReviewAgencyOpeningApplication(ctx, req)
} }
func (c *GRPCClient) ListActivityTemplates(ctx context.Context, req *activityv1.ListActivityTemplatesRequest) (*activityv1.ListActivityTemplatesResponse, error) {
return c.activityTemplateClient.ListActivityTemplates(ctx, req)
}
func (c *GRPCClient) CreateActivityTemplate(ctx context.Context, req *activityv1.CreateActivityTemplateRequest) (*activityv1.CreateActivityTemplateResponse, error) {
return c.activityTemplateClient.CreateActivityTemplate(ctx, req)
}
func (c *GRPCClient) GetActivityTemplate(ctx context.Context, req *activityv1.GetActivityTemplateRequest) (*activityv1.GetActivityTemplateResponse, error) {
return c.activityTemplateClient.GetActivityTemplate(ctx, req)
}
func (c *GRPCClient) UpdateActivityTemplate(ctx context.Context, req *activityv1.UpdateActivityTemplateRequest) (*activityv1.UpdateActivityTemplateResponse, error) {
return c.activityTemplateClient.UpdateActivityTemplate(ctx, req)
}
func (c *GRPCClient) SetActivityTemplateStatus(ctx context.Context, req *activityv1.SetActivityTemplateStatusRequest) (*activityv1.SetActivityTemplateStatusResponse, error) {
return c.activityTemplateClient.SetActivityTemplateStatus(ctx, req)
}
func (c *GRPCClient) DeleteActivityTemplate(ctx context.Context, req *activityv1.DeleteActivityTemplateRequest) (*activityv1.DeleteActivityTemplateResponse, error) {
return c.activityTemplateClient.DeleteActivityTemplate(ctx, req)
}
func (c *GRPCClient) ListActivityTemplateVersions(ctx context.Context, req *activityv1.ListActivityTemplateVersionsRequest) (*activityv1.ListActivityTemplateVersionsResponse, error) {
return c.activityTemplateClient.ListActivityTemplateVersions(ctx, req)
}
func (c *GRPCClient) CloneActivityTemplate(ctx context.Context, req *activityv1.CloneActivityTemplateRequest) (*activityv1.CloneActivityTemplateResponse, error) {
return c.activityTemplateClient.CloneActivityTemplate(ctx, req)
}
func (c *GRPCClient) ValidateActivityTemplate(ctx context.Context, req *activityv1.ValidateActivityTemplateRequest) (*activityv1.ValidateActivityTemplateResponse, error) {
return c.activityTemplateClient.ValidateActivityTemplate(ctx, req)
}
func (c *GRPCClient) ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.AdminListActivityTemplateLeaderboardRequest) (*activityv1.AdminListActivityTemplateLeaderboardResponse, error) {
return c.activityTemplateClient.ListActivityTemplateLeaderboard(ctx, req)
}
func (c *GRPCClient) ListActivityTemplateRewardJobs(ctx context.Context, req *activityv1.ListActivityTemplateRewardJobsRequest) (*activityv1.ListActivityTemplateRewardJobsResponse, error) {
return c.activityTemplateClient.ListActivityTemplateRewardJobs(ctx, req)
}
func (c *GRPCClient) RetryActivityTemplateRewardJob(ctx context.Context, req *activityv1.RetryActivityTemplateRewardJobRequest) (*activityv1.RetryActivityTemplateRewardJobResponse, error) {
return c.activityTemplateClient.RetryActivityTemplateRewardJob(ctx, req)
}
func (c *GRPCClient) ListActivityTemplateTaskClaims(ctx context.Context, req *activityv1.ListActivityTemplateTaskClaimsRequest) (*activityv1.ListActivityTemplateTaskClaimsResponse, error) {
return c.activityTemplateClient.ListActivityTemplateTaskClaims(ctx, req)
}
func (c *GRPCClient) RetryActivityTemplateTaskClaim(ctx context.Context, req *activityv1.RetryActivityTemplateTaskClaimRequest) (*activityv1.RetryActivityTemplateTaskClaimResponse, error) {
return c.activityTemplateClient.RetryActivityTemplateTaskClaim(ctx, req)
}
func (c *GRPCClient) GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error) { func (c *GRPCClient) GetCPWeeklyRankConfig(ctx context.Context, req *activityv1.GetCPWeeklyRankConfigRequest) (*activityv1.GetCPWeeklyRankConfigResponse, error) {
return c.cpWeeklyRankClient.GetCPWeeklyRankConfig(ctx, req) return c.cpWeeklyRankClient.GetCPWeeklyRankConfig(ctx, req)
} }

View File

@ -0,0 +1,148 @@
package statisticsclient
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
type ActivityTemplateDataQuery struct {
AppCode string
TemplateID string
PublishedVersion int64
RegionID int64
StartMS int64
EndMS int64
}
type ActivityTemplateOverview struct {
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
PublishedVersion int64 `json:"published_version"`
Participants int64 `json:"participants"`
VisitCount int64 `json:"visit_count"`
GiftUsers int64 `json:"gift_users"`
TaskUsers int64 `json:"task_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
type ActivityTemplateTrendPoint struct {
StatDay string `json:"stat_day"`
Participants int64 `json:"participants"`
VisitCount int64 `json:"visit_count"`
GiftUsers int64 `json:"gift_users"`
TaskUsers int64 `json:"task_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
type ActivityTemplateGiftStat struct {
GiftID string `json:"gift_id"`
GiftUsers int64 `json:"gift_users"`
GiftEventCount int64 `json:"gift_event_count"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
}
type ActivityTemplateTaskStat struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TaskUsers int64 `json:"task_users"`
TaskProgress int64 `json:"task_progress"`
TaskCompleted int64 `json:"task_completed"`
TaskClaimed int64 `json:"task_claimed"`
}
// ActivityTemplateData mirrors statistics-service's aggregate-only read model.
// Admin intentionally does not reconstruct any of these counters from activity owner tables.
type ActivityTemplateData struct {
Overview ActivityTemplateOverview `json:"overview"`
Trend []ActivityTemplateTrendPoint `json:"trend"`
Gifts []ActivityTemplateGiftStat `json:"gifts"`
Tasks []ActivityTemplateTaskStat `json:"tasks"`
Granularity string `json:"granularity"`
Timezone string `json:"timezone"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
RegionID int64 `json:"region_id"`
DimensionLimit int `json:"dimension_limit"`
GiftSort string `json:"gift_sort"`
TaskSort string `json:"task_sort"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type ActivityTemplateDataClient interface {
ActivityTemplateData(context.Context, ActivityTemplateDataQuery) (ActivityTemplateData, error)
}
type HTTPClient struct {
baseURL string
client *http.Client
}
func NewHTTP(baseURL string, timeout time.Duration) *HTTPClient {
if timeout <= 0 {
timeout = 3 * time.Second
}
return &HTTPClient{
baseURL: strings.TrimRight(strings.TrimSpace(baseURL), "/"),
client: &http.Client{Timeout: timeout},
}
}
func (c *HTTPClient) ActivityTemplateData(ctx context.Context, query ActivityTemplateDataQuery) (ActivityTemplateData, error) {
values := url.Values{}
values.Set("app_code", strings.TrimSpace(query.AppCode))
values.Set("template_id", strings.TrimSpace(query.TemplateID))
values.Set("published_version", strconv.FormatInt(query.PublishedVersion, 10))
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
values.Set("end_ms", strconv.FormatInt(query.EndMS, 10))
if query.RegionID > 0 {
values.Set("region_id", strconv.FormatInt(query.RegionID, 10))
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/internal/v1/statistics/activity-templates/data?"+values.Encode(), nil)
if err != nil {
return ActivityTemplateData{}, err
}
resp, err := c.client.Do(req)
if err != nil {
return ActivityTemplateData{}, err
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
var body struct {
Error string `json:"error"`
}
_ = json.NewDecoder(resp.Body).Decode(&body)
return ActivityTemplateData{}, fmt.Errorf("statistics service returned status %d: %s", resp.StatusCode, strings.TrimSpace(body.Error))
}
var out ActivityTemplateData
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ActivityTemplateData{}, err
}
// Arrays must remain arrays for dense table/chart clients; nil would serialize as null and force needless branches.
if out.Trend == nil {
out.Trend = []ActivityTemplateTrendPoint{}
}
if out.Gifts == nil {
out.Gifts = []ActivityTemplateGiftStat{}
}
if out.Tasks == nil {
out.Tasks = []ActivityTemplateTaskStat{}
}
return out, nil
}

View File

@ -0,0 +1,304 @@
package activitytemplate
import (
"strconv"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/statisticsclient"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
const maxActivityTemplateDataRange = 366 * 24 * time.Hour
func (h *Handler) Leaderboard(c *gin.Context) {
options := shared.ListOptions(c)
versionNo, ok := positiveInt64Query(c, "version_no", true)
if !ok {
response.BadRequest(c, "version_no 参数不正确")
return
}
boardType := strings.ToLower(strings.TrimSpace(c.Query("board_type")))
periodKey := strings.TrimSpace(c.Query("period_key"))
if boardType != "daily" && boardType != "total" {
response.BadRequest(c, "board_type 参数不正确")
return
}
if boardType == "daily" {
if parsed, err := time.Parse("2006-01-02", periodKey); err != nil || parsed.Format("2006-01-02") != periodKey {
response.BadRequest(c, "daily 榜单必须提供 YYYY-MM-DD 格式的 period_key")
return
}
} else {
periodKey = "total"
}
resp, err := h.activity.ListActivityTemplateLeaderboard(c.Request.Context(), &activityv1.AdminListActivityTemplateLeaderboardRequest{
Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
BoardType: boardType, PeriodKey: periodKey, Page: int32(options.Page), PageSize: int32(options.PageSize),
})
if err != nil {
h.writeGRPCError(c, err, "获取活动模版榜单失败")
return
}
entries := make([]leaderboardEntryDTO, 0, len(resp.GetEntries()))
users := h.activityTemplateUsers(c, leaderboardProtoUserIDs(resp.GetEntries()))
for _, item := range resp.GetEntries() {
entries = append(entries, leaderboardEntryFromProto(item, users))
}
response.OK(c, leaderboardPageDTO{
Period: rankPeriodFromProto(resp.GetPeriod()), Items: entries,
Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal(),
})
}
func (h *Handler) RewardJobs(c *gin.Context) {
options := shared.ListOptions(c)
versionNo, ok := positiveInt64Query(c, "version_no", true)
if !ok {
response.BadRequest(c, "version_no 参数不正确")
return
}
boardType := strings.ToLower(strings.TrimSpace(c.Query("board_type")))
if boardType != "" && boardType != "daily" && boardType != "total" {
response.BadRequest(c, "board_type 参数不正确")
return
}
status := strings.ToLower(strings.TrimSpace(c.Query("status")))
if status != "" && status != "pending" && status != "running" && status != "granted" && status != "failed" && status != "dead" {
response.BadRequest(c, "status 参数不正确")
return
}
periodKey := strings.TrimSpace(c.Query("period_key"))
if boardType == "total" && periodKey != "" {
periodKey = "total"
}
resp, err := h.activity.ListActivityTemplateRewardJobs(c.Request.Context(), &activityv1.ListActivityTemplateRewardJobsRequest{
Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
BoardType: boardType, PeriodKey: periodKey, Status: status, Page: int32(options.Page), PageSize: int32(options.PageSize),
})
if err != nil {
h.writeGRPCError(c, err, "获取活动模版奖励发放记录失败")
return
}
items := make([]rewardJobDTO, 0, len(resp.GetJobs()))
users := h.activityTemplateUsers(c, rewardJobProtoUserIDs(resp.GetJobs()))
for _, item := range resp.GetJobs() {
items = append(items, rewardJobFromProto(item, users))
}
response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) RetryRewardJob(c *gin.Context) {
templateID := strings.TrimSpace(c.Param("template_id"))
jobID := strings.TrimSpace(c.Param("reward_job_id"))
if templateID == "" || jobID == "" {
response.BadRequest(c, "活动模版或奖励任务 ID 不正确")
return
}
resp, err := h.activity.RetryActivityTemplateRewardJob(c.Request.Context(), &activityv1.RetryActivityTemplateRewardJobRequest{
Meta: h.meta(c), TemplateId: templateID, RewardJobId: jobID, OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "重试活动模版奖励发放失败")
return
}
users := h.activityTemplateUsers(c, []int64{resp.GetJob().GetUserId()})
item := rewardJobFromProto(resp.GetJob(), users)
shared.OperationLogWithResourceID(c, h.audit, "retry-activity-template-reward", "activity_template_reward_jobs", jobID, "success", "")
response.OK(c, item)
}
func (h *Handler) TaskClaims(c *gin.Context) {
options := shared.ListOptions(c)
versionNo, versionOK := positiveInt64Query(c, "version_no", false)
userID, userOK := positiveInt64Query(c, "user_id", false)
if !versionOK || !userOK {
response.BadRequest(c, "version_no 或 user_id 参数不正确")
return
}
taskDay := strings.TrimSpace(c.Query("task_day"))
if taskDay != "" {
parsed, err := time.Parse("2006-01-02", taskDay)
if err != nil || parsed.Format("2006-01-02") != taskDay {
response.BadRequest(c, "task_day 必须为 YYYY-MM-DD")
return
}
}
status := strings.ToLower(strings.TrimSpace(c.Query("status")))
if status != "" && status != "pending" && status != "running" && status != "granted" && status != "failed" {
response.BadRequest(c, "status 参数不正确")
return
}
resp, err := h.activity.ListActivityTemplateTaskClaims(c.Request.Context(), &activityv1.ListActivityTemplateTaskClaimsRequest{
Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
TaskDay: taskDay, TaskKey: strings.TrimSpace(c.Query("task_key")), UserId: userID, Status: status,
Page: int32(options.Page), PageSize: int32(options.PageSize),
})
if err != nil {
h.writeGRPCError(c, err, "获取活动模版任务领奖记录失败")
return
}
items := make([]taskClaimDTO, 0, len(resp.GetClaims()))
users := h.activityTemplateUsers(c, taskClaimProtoUserIDs(resp.GetClaims()))
for _, item := range resp.GetClaims() {
items = append(items, taskClaimFromProto(item, users))
}
response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) RetryTaskClaim(c *gin.Context) {
templateID := strings.TrimSpace(c.Param("template_id"))
claimID := strings.TrimSpace(c.Param("claim_id"))
if templateID == "" || claimID == "" {
response.BadRequest(c, "活动模版或任务领奖记录 ID 不正确")
return
}
resp, err := h.activity.RetryActivityTemplateTaskClaim(c.Request.Context(), &activityv1.RetryActivityTemplateTaskClaimRequest{
Meta: h.meta(c), TemplateId: templateID, ClaimId: claimID, OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "重试活动模版任务奖励发放失败")
return
}
users := h.activityTemplateUsers(c, []int64{resp.GetClaim().GetUserId()})
item := taskClaimFromProto(resp.GetClaim(), users)
shared.OperationLogWithResourceID(c, h.audit, "retry-activity-template-task-claim", "activity_template_task_claims", claimID, "success", "")
response.OK(c, item)
}
func (h *Handler) activityTemplateUsers(c *gin.Context, userIDs []int64) map[int64]*userclient.User {
if h.users == nil || len(userIDs) == 0 {
return nil
}
unique := make([]int64, 0, len(userIDs))
seen := make(map[int64]struct{}, len(userIDs))
for _, userID := range userIDs {
if userID <= 0 {
continue
}
if _, exists := seen[userID]; exists {
continue
}
seen[userID] = struct{}{}
unique = append(unique, userID)
}
if len(unique) == 0 {
return nil
}
users, err := h.users.BatchGetUsers(c.Request.Context(), userclient.BatchGetUsersRequest{
RequestID: middleware.CurrentRequestID(c), Caller: "admin-server", UserIDs: unique,
})
if err != nil {
// 用户资料是展示装饰,榜单和发奖事实仍必须返回;不能因 user-service 短暂不可用隐藏结算结果。
return nil
}
return users
}
func leaderboardProtoUserIDs(items []*activityv1.ActivityTemplateLeaderboardEntry) []int64 {
userIDs := make([]int64, 0, len(items))
for _, item := range items {
if item != nil {
userIDs = append(userIDs, item.GetUserId())
}
}
return userIDs
}
func rewardJobProtoUserIDs(items []*activityv1.ActivityTemplateRewardJob) []int64 {
userIDs := make([]int64, 0, len(items))
for _, item := range items {
if item != nil {
userIDs = append(userIDs, item.GetUserId())
}
}
return userIDs
}
func taskClaimProtoUserIDs(items []*activityv1.ActivityTemplateTaskClaim) []int64 {
userIDs := make([]int64, 0, len(items))
for _, item := range items {
if item != nil {
userIDs = append(userIDs, item.GetUserId())
}
}
return userIDs
}
func (h *Handler) Data(c *gin.Context) {
if h.stats == nil {
response.ServerError(c, "活动模版统计服务未配置")
return
}
versionNo, ok := positiveInt64Query(c, "version_no", true)
if !ok {
response.BadRequest(c, "version_no 参数不正确")
return
}
startMS, startOK := requiredPositiveInt64Query(c, "start_ms")
endMS, endOK := requiredPositiveInt64Query(c, "end_ms")
if !startOK || !endOK || endMS <= startMS {
response.BadRequest(c, "start_ms 或 end_ms 参数不正确")
return
}
regionID := int64(0)
if raw, exists := c.GetQuery("region_id"); exists {
var err error
regionID, err = strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
if err != nil || regionID <= 0 {
response.BadRequest(c, "region_id 参数不正确")
return
}
}
// statistics-service 按 UTC 日聚合。把任意 UI 时间窗归一为覆盖它的完整 [UTC 00:00, UTC 00:00)
// 既避免半日过滤产生“看似精确但实际丢整日”的静默误差,也由响应 start_ms/end_ms 明确回显生效窗口。
effectiveStartMS := utcDayFloor(startMS)
effectiveEndMS := utcDayCeil(endMS)
if effectiveEndMS-effectiveStartMS > maxActivityTemplateDataRange.Milliseconds() {
response.BadRequest(c, "活动模版数据查询区间不能超过 366 天")
return
}
data, err := h.stats.ActivityTemplateData(c.Request.Context(), statisticsclient.ActivityTemplateDataQuery{
AppCode: appctx.FromContext(c.Request.Context()), TemplateID: strings.TrimSpace(c.Param("template_id")),
PublishedVersion: versionNo, RegionID: regionID, StartMS: effectiveStartMS, EndMS: effectiveEndMS,
})
if err != nil {
response.ServerError(c, "获取活动模版统计数据失败")
return
}
response.OK(c, data)
}
func positiveInt64Query(c *gin.Context, key string, required bool) (int64, bool) {
raw, exists := c.GetQuery(key)
if !exists || strings.TrimSpace(raw) == "" {
return 0, !required
}
value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
return value, err == nil && value > 0
}
func requiredPositiveInt64Query(c *gin.Context, key string) (int64, bool) {
value, ok := positiveInt64Query(c, key, true)
return value, ok
}
func utcDayFloor(ms int64) int64 {
value := time.UnixMilli(ms).UTC()
return time.Date(value.Year(), value.Month(), value.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
}
func utcDayCeil(ms int64) int64 {
floor := utcDayFloor(ms)
if floor == ms {
return floor
}
return floor + 24*time.Hour.Milliseconds()
}

View File

@ -0,0 +1,275 @@
package activitytemplate
import (
"context"
"strconv"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/integration/statisticsclient"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
)
type Handler struct {
activity activityclient.Client
stats statisticsclient.ActivityTemplateDataClient
users UserBatchGetter
audit shared.OperationLogger
}
type UserBatchGetter interface {
BatchGetUsers(context.Context, userclient.BatchGetUsersRequest) (map[int64]*userclient.User, error)
}
func New(activity activityclient.Client, audit shared.OperationLogger, dataClients ...statisticsclient.ActivityTemplateDataClient) *Handler {
handler := &Handler{activity: activity, audit: audit}
if len(dataClients) > 0 {
handler.stats = dataClients[0]
}
return handler
}
func (h *Handler) BindUserBatchGetter(users UserBatchGetter) *Handler {
if h != nil {
h.users = users
}
return h
}
func (h *Handler) List(c *gin.Context) {
options := shared.ListOptions(c)
startMS, startOK := parseOptionalNonNegativeInt64Query(c, "start_ms")
endMS, endOK := parseOptionalNonNegativeInt64Query(c, "end_ms")
if !startOK || !endOK {
response.BadRequest(c, "start_ms 或 end_ms 参数不正确")
return
}
req := &activityv1.ListActivityTemplatesRequest{
Meta: h.meta(c),
Keyword: strings.TrimSpace(options.Keyword),
Status: strings.TrimSpace(options.Status),
StartMs: startMS,
EndMs: endMS,
Page: int32(options.Page),
PageSize: int32(options.PageSize),
}
if raw, exists := c.GetQuery("region_id"); exists {
regionID, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
if err != nil || regionID <= 0 {
response.BadRequest(c, "region_id 参数不正确")
return
}
req.RegionId = &regionID
}
if raw, exists := c.GetQuery("all_regions"); exists {
allRegions, err := strconv.ParseBool(strings.TrimSpace(raw))
if err != nil {
response.BadRequest(c, "all_regions 参数不正确")
return
}
req.AllRegions = &allRegions
}
resp, err := h.activity.ListActivityTemplates(c.Request.Context(), req)
if err != nil {
h.writeGRPCError(c, err, "获取活动模版失败")
return
}
items := make([]templateSummaryDTO, 0, len(resp.GetTemplates()))
for _, item := range resp.GetTemplates() {
items = append(items, summaryFromProto(item))
}
response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal(), ServerTimeMS: resp.GetServerTimeMs()})
}
func (h *Handler) Create(c *gin.Context) {
var req templateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "活动模版参数不正确")
return
}
resp, err := h.activity.CreateActivityTemplate(c.Request.Context(), &activityv1.CreateActivityTemplateRequest{
Meta: h.meta(c), Template: req.toProto(""), OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "创建活动模版失败")
return
}
item := templateFromProto(resp.GetTemplate())
shared.OperationLogWithResourceID(c, h.audit, "create-activity-template", "activity_templates", item.TemplateID, "success", "")
response.Created(c, item)
}
func (h *Handler) Get(c *gin.Context) {
templateID := strings.TrimSpace(c.Param("template_id"))
resp, err := h.activity.GetActivityTemplate(c.Request.Context(), &activityv1.GetActivityTemplateRequest{Meta: h.meta(c), TemplateId: templateID})
if err != nil {
h.writeGRPCError(c, err, "获取活动模版详情失败")
return
}
response.OK(c, templateFromProto(resp.GetTemplate()))
}
func (h *Handler) Update(c *gin.Context) {
var req templateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "活动模版参数不正确")
return
}
resp, err := h.activity.UpdateActivityTemplate(c.Request.Context(), &activityv1.UpdateActivityTemplateRequest{
Meta: h.meta(c), Template: req.toProto(strings.TrimSpace(c.Param("template_id"))),
ExpectedRevision: req.ExpectedRevision, OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "更新活动模版失败")
return
}
item := templateFromProto(resp.GetTemplate())
shared.OperationLogWithResourceID(c, h.audit, "update-activity-template", "activity_templates", item.TemplateID, "success", "")
response.OK(c, item)
}
func (h *Handler) SetStatus(c *gin.Context) {
var req statusRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "活动模版状态参数不正确")
return
}
templateID := strings.TrimSpace(c.Param("template_id"))
resp, err := h.activity.SetActivityTemplateStatus(c.Request.Context(), &activityv1.SetActivityTemplateStatusRequest{
Meta: h.meta(c), TemplateId: templateID, Status: strings.TrimSpace(req.Status),
ExpectedRevision: req.ExpectedRevision, OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "更新活动模版状态失败")
return
}
item := templateFromProto(resp.GetTemplate())
shared.OperationLogWithResourceID(c, h.audit, "set-activity-template-status", "activity_templates", item.TemplateID, "success", "")
response.OK(c, item)
}
func (h *Handler) Delete(c *gin.Context) {
expectedRevision := parseInt64Query(c, "revision")
if expectedRevision <= 0 {
response.BadRequest(c, "revision 参数不正确")
return
}
templateID := strings.TrimSpace(c.Param("template_id"))
resp, err := h.activity.DeleteActivityTemplate(c.Request.Context(), &activityv1.DeleteActivityTemplateRequest{
Meta: h.meta(c), TemplateId: templateID, ExpectedRevision: expectedRevision,
OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "删除活动模版失败")
return
}
item := templateFromProto(resp.GetTemplate())
shared.OperationLogWithResourceID(c, h.audit, "archive-activity-template", "activity_templates", item.TemplateID, "success", "")
response.OK(c, gin.H{"template": item, "archived": resp.GetArchived()})
}
func (h *Handler) Versions(c *gin.Context) {
options := shared.ListOptions(c)
resp, err := h.activity.ListActivityTemplateVersions(c.Request.Context(), &activityv1.ListActivityTemplateVersionsRequest{
Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), Page: int32(options.Page), PageSize: int32(options.PageSize),
})
if err != nil {
h.writeGRPCError(c, err, "获取活动模版版本失败")
return
}
items := make([]versionDTO, 0, len(resp.GetVersions()))
for _, version := range resp.GetVersions() {
items = append(items, versionDTO{
TemplateID: version.GetTemplateId(), VersionNo: version.GetVersionNo(), Snapshot: templateFromProto(version.GetSnapshot()),
PublishedByAdminID: version.GetPublishedByAdminId(), PublishedAtMS: version.GetPublishedAtMs(),
RuntimeFromMS: version.GetRuntimeFromMs(), RuntimeToMS: version.GetRuntimeToMs(),
})
}
response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) Clone(c *gin.Context) {
var req cloneRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "复制活动模版参数不正确")
return
}
resp, err := h.activity.CloneActivityTemplate(c.Request.Context(), &activityv1.CloneActivityTemplateRequest{
Meta: h.meta(c), SourceTemplateId: strings.TrimSpace(c.Param("template_id")), SourceVersion: req.SourceVersion,
TemplateCode: strings.TrimSpace(req.TemplateCode), Name: strings.TrimSpace(req.Name), OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
h.writeGRPCError(c, err, "复制活动模版失败")
return
}
item := templateFromProto(resp.GetTemplate())
shared.OperationLogWithResourceID(c, h.audit, "clone-activity-template", "activity_templates", item.TemplateID, "success", "")
response.Created(c, item)
}
func (h *Handler) Validate(c *gin.Context) {
var req validateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "活动模版校验参数不正确")
return
}
resp, err := h.activity.ValidateActivityTemplate(c.Request.Context(), &activityv1.ValidateActivityTemplateRequest{
Meta: h.meta(c), Template: req.Template.toProto(""), ForPublish: req.ForPublish,
})
if err != nil {
h.writeGRPCError(c, err, "校验活动模版失败")
return
}
issues := make([]validationIssueDTO, 0, len(resp.GetIssues()))
for _, issue := range resp.GetIssues() {
issues = append(issues, validationIssueDTO{Field: issue.GetField(), Code: issue.GetCode(), Message: issue.GetMessage()})
}
response.OK(c, gin.H{"valid": resp.GetValid(), "issues": issues})
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c), Caller: "admin-server", AppCode: appctx.FromContext(c.Request.Context()),
SentAtMs: time.Now().UTC().UnixMilli(),
}
}
// writeGRPCError 把 owner service 的并发和状态冲突稳定映射为 HTTP 409
// 不能把 revision 冲突降级成 400否则编辑器无法区分字段错误与“别人已经保存过”。
func (h *Handler) writeGRPCError(c *gin.Context, err error, fallback string) {
status := grpcstatus.Convert(err)
switch status.Code() {
case codes.InvalidArgument:
response.BadRequest(c, status.Message())
case codes.NotFound:
response.NotFound(c, status.Message())
case codes.AlreadyExists, codes.FailedPrecondition, codes.Aborted:
response.Conflict(c, status.Message())
default:
response.ServerError(c, fallback)
}
}
func parseInt64Query(c *gin.Context, key string) int64 {
value, _ := strconv.ParseInt(strings.TrimSpace(c.Query(key)), 10, 64)
return value
}
func parseOptionalNonNegativeInt64Query(c *gin.Context, key string) (int64, bool) {
raw, exists := c.GetQuery(key)
if !exists {
return 0, true
}
value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
return value, err == nil && value >= 0
}

View File

@ -0,0 +1,412 @@
package activitytemplate
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/integration/statisticsclient"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/middleware"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
type revisionConflictClient struct {
activityclient.Client
}
type activityTemplateDataOwnerClient struct {
activityclient.Client
leaderboard *activityv1.AdminListActivityTemplateLeaderboardResponse
rewards *activityv1.ListActivityTemplateRewardJobsResponse
retryReward *activityv1.RetryActivityTemplateRewardJobResponse
claims *activityv1.ListActivityTemplateTaskClaimsResponse
retryClaim *activityv1.RetryActivityTemplateTaskClaimResponse
lastClaims *activityv1.ListActivityTemplateTaskClaimsRequest
lastRetry *activityv1.RetryActivityTemplateTaskClaimRequest
lastRewards *activityv1.ListActivityTemplateRewardJobsRequest
lastRewardRetry *activityv1.RetryActivityTemplateRewardJobRequest
}
type activityTemplateVersionsOwnerClient struct {
activityclient.Client
response *activityv1.ListActivityTemplateVersionsResponse
}
func (f activityTemplateVersionsOwnerClient) ListActivityTemplateVersions(context.Context, *activityv1.ListActivityTemplateVersionsRequest) (*activityv1.ListActivityTemplateVersionsResponse, error) {
return f.response, nil
}
func (f activityTemplateDataOwnerClient) ListActivityTemplateLeaderboard(context.Context, *activityv1.AdminListActivityTemplateLeaderboardRequest) (*activityv1.AdminListActivityTemplateLeaderboardResponse, error) {
return f.leaderboard, nil
}
func (f *activityTemplateDataOwnerClient) ListActivityTemplateRewardJobs(_ context.Context, req *activityv1.ListActivityTemplateRewardJobsRequest) (*activityv1.ListActivityTemplateRewardJobsResponse, error) {
f.lastRewards = req
return f.rewards, nil
}
func (f *activityTemplateDataOwnerClient) RetryActivityTemplateRewardJob(_ context.Context, req *activityv1.RetryActivityTemplateRewardJobRequest) (*activityv1.RetryActivityTemplateRewardJobResponse, error) {
f.lastRewardRetry = req
return f.retryReward, nil
}
func (f *activityTemplateDataOwnerClient) ListActivityTemplateTaskClaims(_ context.Context, req *activityv1.ListActivityTemplateTaskClaimsRequest) (*activityv1.ListActivityTemplateTaskClaimsResponse, error) {
f.lastClaims = req
return f.claims, nil
}
func (f *activityTemplateDataOwnerClient) RetryActivityTemplateTaskClaim(_ context.Context, req *activityv1.RetryActivityTemplateTaskClaimRequest) (*activityv1.RetryActivityTemplateTaskClaimResponse, error) {
f.lastRetry = req
return f.retryClaim, nil
}
type captureActivityTemplateUsers struct {
calls int
userIDs []int64
users map[int64]*userclient.User
err error
}
func (f *captureActivityTemplateUsers) BatchGetUsers(_ context.Context, req userclient.BatchGetUsersRequest) (map[int64]*userclient.User, error) {
f.calls++
f.userIDs = append([]int64(nil), req.UserIDs...)
return f.users, f.err
}
type captureActivityTemplateDataClient struct {
query statisticsclient.ActivityTemplateDataQuery
}
func (c *captureActivityTemplateDataClient) ActivityTemplateData(_ context.Context, query statisticsclient.ActivityTemplateDataQuery) (statisticsclient.ActivityTemplateData, error) {
c.query = query
return statisticsclient.ActivityTemplateData{
Overview: statisticsclient.ActivityTemplateOverview{TemplateID: query.TemplateID, PublishedVersion: query.PublishedVersion, VisitCount: 13},
Trend: []statisticsclient.ActivityTemplateTrendPoint{{StatDay: "2026-07-14", VisitCount: 7}}, Gifts: []statisticsclient.ActivityTemplateGiftStat{},
Tasks: []statisticsclient.ActivityTemplateTaskStat{}, Granularity: "day", Timezone: "UTC",
StartMS: query.StartMS, EndMS: query.EndMS, RegionID: query.RegionID,
}, nil
}
func (revisionConflictClient) UpdateActivityTemplate(context.Context, *activityv1.UpdateActivityTemplateRequest) (*activityv1.UpdateActivityTemplateResponse, error) {
return nil, status.Error(codes.FailedPrecondition, "activity template revision changed")
}
func TestUpdateMapsRevisionConflictToHTTP409(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPut, "/api/v1/admin/activities/templates/acttpl_1", strings.NewReader(`{
"template_code":"gift_challenge_2026",
"name":"Gift Challenge",
"activity_type":"gift_challenge",
"expected_revision":7
}`))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
New(revisionConflictClient{}, nil).Update(ctx)
if recorder.Code != http.StatusConflict {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusConflict, recorder.Body.String())
}
var body struct {
Code int `json:"code"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatalf("decode response: %v", err)
}
if body.Code != 40900 {
t.Fatalf("response code = %d, want 40900", body.Code)
}
}
func TestListRejectsMalformedTimeFilterBeforeCallingOwnerService(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates?start_ms=not-a-time", nil)
New(nil, nil).List(ctx)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
}
}
func TestVersionsExposeActualRuntimeWindow(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/versions?page=1&page_size=20", nil)
ctx.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
client := activityTemplateVersionsOwnerClient{response: &activityv1.ListActivityTemplateVersionsResponse{
Total: 1,
Versions: []*activityv1.ActivityTemplateVersion{{
TemplateId: "acttpl_1", VersionNo: 1, Snapshot: &activityv1.ActivityTemplate{TemplateId: "acttpl_1", LifecycleStatus: "disabled"},
PublishedByAdminId: 90001, PublishedAtMs: 100, RuntimeFromMs: 100, RuntimeToMs: 500,
}},
}}
New(client, nil).Versions(ctx)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d; body=%s", recorder.Code, recorder.Body.String())
}
var body struct {
Data struct {
Items []struct {
RuntimeFromMS int64 `json:"runtime_from_ms"`
RuntimeToMS int64 `json:"runtime_to_ms"`
Snapshot struct {
LifecycleStatus string `json:"lifecycle_status"`
} `json:"snapshot"`
} `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(body.Data.Items) != 1 || body.Data.Items[0].RuntimeFromMS != 100 || body.Data.Items[0].RuntimeToMS != 500 || body.Data.Items[0].Snapshot.LifecycleStatus != "disabled" {
t.Fatalf("version runtime response = %+v", body.Data.Items)
}
}
func TestDataNormalizesRequestedWindowToUTCWholeDays(t *testing.T) {
gin.SetMode(gin.TestMode)
stats := &captureActivityTemplateDataClient{}
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
start := time.Date(2026, 7, 14, 12, 30, 0, 0, time.UTC).UnixMilli()
end := time.Date(2026, 7, 15, 8, 45, 0, 0, time.UTC).UnixMilli()
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/data?version_no=3&region_id=7&start_ms="+strconv.FormatInt(start, 10)+"&end_ms="+strconv.FormatInt(end, 10), nil)
ctx.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
New(nil, nil, stats).Data(ctx)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d; body=%s", recorder.Code, recorder.Body.String())
}
wantStart := time.Date(2026, 7, 14, 0, 0, 0, 0, time.UTC).UnixMilli()
wantEnd := time.Date(2026, 7, 16, 0, 0, 0, 0, time.UTC).UnixMilli()
if stats.query.TemplateID != "acttpl_1" || stats.query.PublishedVersion != 3 || stats.query.RegionID != 7 ||
stats.query.StartMS != wantStart || stats.query.EndMS != wantEnd {
t.Fatalf("normalized statistics query = %+v", stats.query)
}
var body struct {
Data statisticsclient.ActivityTemplateData `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatalf("decode response: %v", err)
}
if body.Data.StartMS != wantStart || body.Data.EndMS != wantEnd || body.Data.Granularity != "day" || body.Data.Timezone != "UTC" ||
body.Data.Overview.VisitCount != 13 || len(body.Data.Trend) != 1 || body.Data.Trend[0].VisitCount != 7 {
t.Fatalf("effective range not echoed: %+v", body.Data)
}
}
func TestLeaderboardAndRewardsBatchDecorateUsersWithoutOwningProfileFacts(t *testing.T) {
gin.SetMode(gin.TestMode)
owner := activityTemplateDataOwnerClient{
leaderboard: &activityv1.AdminListActivityTemplateLeaderboardResponse{
Period: &activityv1.ActivityTemplateRankPeriod{TemplateId: "acttpl_1", BoardType: "daily", PeriodKey: "2026-07-14"},
Entries: []*activityv1.ActivityTemplateLeaderboardEntry{
{RankNo: 1, UserId: 501, Score: 900}, {RankNo: 2, UserId: 501, Score: 800}, {RankNo: 3, UserId: 502, Score: 700},
}, Total: 3,
},
rewards: &activityv1.ListActivityTemplateRewardJobsResponse{
Jobs: []*activityv1.ActivityTemplateRewardJob{{RewardJobId: "job-1", UserId: 502, Status: "granted"}}, Total: 1,
},
}
users := &captureActivityTemplateUsers{users: map[int64]*userclient.User{
501: {UserID: 501, DisplayUserID: "L501", Username: "first", Avatar: "first.png"},
502: {UserID: 502, DisplayUserID: "L502", Username: "second", Avatar: "second.png"},
}}
handler := New(&owner, nil).BindUserBatchGetter(users)
leaderboardRecorder := httptest.NewRecorder()
leaderboardContext, _ := gin.CreateTestContext(leaderboardRecorder)
leaderboardContext.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/leaderboard?version_no=1&board_type=daily&period_key=2026-07-14", nil)
leaderboardContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
handler.Leaderboard(leaderboardContext)
if leaderboardRecorder.Code != http.StatusOK || users.calls != 1 || len(users.userIDs) != 2 || users.userIDs[0] != 501 || users.userIDs[1] != 502 {
t.Fatalf("leaderboard batch enrichment: status=%d calls=%d ids=%v body=%s", leaderboardRecorder.Code, users.calls, users.userIDs, leaderboardRecorder.Body.String())
}
var leaderboardBody struct {
Data struct {
Items []struct {
User *activityTemplateUserDTO `json:"user"`
} `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(leaderboardRecorder.Body.Bytes(), &leaderboardBody); err != nil || len(leaderboardBody.Data.Items) != 3 || leaderboardBody.Data.Items[0].User == nil || leaderboardBody.Data.Items[0].User.DisplayUserID != "L501" {
t.Fatalf("leaderboard user decoration mismatch: err=%v body=%s", err, leaderboardRecorder.Body.String())
}
users.calls, users.userIDs = 0, nil
rewardsRecorder := httptest.NewRecorder()
rewardsContext, _ := gin.CreateTestContext(rewardsRecorder)
rewardsContext.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/rewards?version_no=1", nil)
rewardsContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
handler.RewardJobs(rewardsContext)
if rewardsRecorder.Code != http.StatusOK || users.calls != 1 || len(users.userIDs) != 1 || users.userIDs[0] != 502 || !strings.Contains(rewardsRecorder.Body.String(), `"display_user_id":"L502"`) {
t.Fatalf("reward user decoration: status=%d calls=%d ids=%v body=%s", rewardsRecorder.Code, users.calls, users.userIDs, rewardsRecorder.Body.String())
}
}
func TestRewardJobsRequiresPublishedVersion(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/rewards", nil)
ctx.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
New(&activityTemplateDataOwnerClient{}, nil).RewardJobs(ctx)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
}
}
func TestRewardJobsExposeDeadBackoffAndAllowExplicitRevival(t *testing.T) {
gin.SetMode(gin.TestMode)
dead := &activityv1.ActivityTemplateRewardJob{
RewardJobId: "job-dead", TemplateId: "acttpl_1", VersionNo: 2, UserId: 502,
Status: "dead", FailureReason: "maximum delivery attempts exhausted", AttemptCount: 8,
MaxAttempts: 8, NextRetryAtMs: 0,
}
running := proto.Clone(dead).(*activityv1.ActivityTemplateRewardJob)
running.Status, running.AttemptCount, running.MaxAttempts = "running", 9, 9
owner := &activityTemplateDataOwnerClient{
rewards: &activityv1.ListActivityTemplateRewardJobsResponse{Jobs: []*activityv1.ActivityTemplateRewardJob{dead}, Total: 1},
retryReward: &activityv1.RetryActivityTemplateRewardJobResponse{Job: running},
}
handler := New(owner, nil)
listRecorder := httptest.NewRecorder()
listContext, _ := gin.CreateTestContext(listRecorder)
listContext.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/rewards?version_no=2&status=dead", nil)
listContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
handler.RewardJobs(listContext)
if listRecorder.Code != http.StatusOK || owner.lastRewards == nil || owner.lastRewards.GetStatus() != "dead" ||
!strings.Contains(listRecorder.Body.String(), `"max_attempts":8`) || !strings.Contains(listRecorder.Body.String(), `"next_retry_at_ms":0`) {
t.Fatalf("dead reward list mismatch: status=%d request=%+v body=%s", listRecorder.Code, owner.lastRewards, listRecorder.Body.String())
}
retryRecorder := httptest.NewRecorder()
retryContext, _ := gin.CreateTestContext(retryRecorder)
retryContext.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/activities/templates/acttpl_1/rewards/job-dead/retry", nil)
retryContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}, {Key: "reward_job_id", Value: "job-dead"}}
retryContext.Set(middleware.ContextUserID, uint(90001))
handler.RetryRewardJob(retryContext)
if retryRecorder.Code != http.StatusOK || owner.lastRewardRetry == nil || owner.lastRewardRetry.GetRewardJobId() != "job-dead" ||
owner.lastRewardRetry.GetOperatorAdminId() != 90001 || !strings.Contains(retryRecorder.Body.String(), `"max_attempts":9`) {
t.Fatalf("dead reward revival mismatch: status=%d request=%+v body=%s", retryRecorder.Code, owner.lastRewardRetry, retryRecorder.Body.String())
}
}
func TestTaskClaimsFilterEnrichAndRetryPreserveImmutableAttribution(t *testing.T) {
gin.SetMode(gin.TestMode)
rewardItem := &activityv1.ActivityTemplateRewardItem{ItemType: "resource", ResourceId: 91, ResourceType: "avatar_frame", Name: "Frame", Quantity: 1}
claim := &activityv1.ActivityTemplateTaskClaim{
ClaimId: "claim-1", CommandId: "device-command", TemplateId: "acttpl_1", TemplateCode: "summer",
VersionNo: 3, TaskDay: "2026-07-14", TaskKey: "send_gift", UserId: 503,
RewardResourceGroupId: 81, WalletCommandId: "wallet-command", WalletGrantId: "wallet-grant",
Status: "failed", FailureReason: "timeout", AttemptCount: 2,
RewardSnapshotId: "rgs-task", RewardSnapshotHash: strings.Repeat("a", 64), RewardName: "Task reward",
RewardItems: []*activityv1.ActivityTemplateRewardItem{rewardItem}, EarningRegionId: 210, AttributionAtMs: 1784073000000,
}
owner := &activityTemplateDataOwnerClient{
claims: &activityv1.ListActivityTemplateTaskClaimsResponse{Claims: []*activityv1.ActivityTemplateTaskClaim{claim}, Total: 1},
retryClaim: &activityv1.RetryActivityTemplateTaskClaimResponse{Claim: claim},
}
users := &captureActivityTemplateUsers{users: map[int64]*userclient.User{
503: {UserID: 503, DisplayUserID: "L503", Username: "winner", Avatar: "winner.png"},
}}
handler := New(owner, nil).BindUserBatchGetter(users)
listRecorder := httptest.NewRecorder()
listContext, _ := gin.CreateTestContext(listRecorder)
listContext.Request = httptest.NewRequest(http.MethodGet,
"/api/v1/admin/activities/templates/acttpl_1/task-claims?version_no=3&task_day=2026-07-14&task_key=send_gift&user_id=503&status=failed&page=2&page_size=10", nil)
listContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
handler.TaskClaims(listContext)
if listRecorder.Code != http.StatusOK || owner.lastClaims == nil || owner.lastClaims.GetVersionNo() != 3 ||
owner.lastClaims.GetTaskDay() != "2026-07-14" || owner.lastClaims.GetTaskKey() != "send_gift" ||
owner.lastClaims.GetUserId() != 503 || owner.lastClaims.GetStatus() != "failed" || owner.lastClaims.GetPage() != 2 || owner.lastClaims.GetPageSize() != 10 {
t.Fatalf("task claim filters mismatch: status=%d request=%+v body=%s", listRecorder.Code, owner.lastClaims, listRecorder.Body.String())
}
var listBody struct {
Data struct {
Items []taskClaimDTO `json:"items"`
} `json:"data"`
}
if err := json.Unmarshal(listRecorder.Body.Bytes(), &listBody); err != nil || len(listBody.Data.Items) != 1 {
t.Fatalf("decode task claims: err=%v body=%s", err, listRecorder.Body.String())
}
item := listBody.Data.Items[0]
if item.RewardSnapshotHash != strings.Repeat("a", 64) || item.RewardName != "Task reward" || len(item.RewardItems) != 1 ||
item.EarningRegionID != 210 || item.AttributionAtMS != 1784073000000 || item.User == nil || item.User.DisplayUserID != "L503" {
t.Fatalf("task claim immutable/admin view mismatch: %+v", item)
}
retryRecorder := httptest.NewRecorder()
retryContext, _ := gin.CreateTestContext(retryRecorder)
retryContext.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/activities/templates/acttpl_1/task-claims/claim-1/retry", nil)
retryContext.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}, {Key: "claim_id", Value: "claim-1"}}
retryContext.Set(middleware.ContextUserID, uint(90001))
handler.RetryTaskClaim(retryContext)
if retryRecorder.Code != http.StatusOK || owner.lastRetry == nil || owner.lastRetry.GetTemplateId() != "acttpl_1" ||
owner.lastRetry.GetClaimId() != "claim-1" || owner.lastRetry.GetOperatorAdminId() != 90001 {
t.Fatalf("task claim retry mismatch: status=%d request=%+v body=%s", retryRecorder.Code, owner.lastRetry, retryRecorder.Body.String())
}
}
func TestAdminRewardDTOsExposePublishedSnapshots(t *testing.T) {
item := &activityv1.ActivityTemplateRewardItem{ItemType: "wallet_asset", WalletAssetType: "coin", WalletAssetAmount: 500, Name: "Coins"}
template := templateFromProto(&activityv1.ActivityTemplate{
Tasks: []*activityv1.ActivityTemplateTask{{TaskKey: "visit", RewardSnapshotId: "task-snapshot", RewardSnapshotHash: strings.Repeat("b", 64), RewardName: "Visit reward", RewardItems: []*activityv1.ActivityTemplateRewardItem{item}}},
RankRewards: []*activityv1.ActivityTemplateRankReward{{BoardType: "total", RankFrom: 1, RankTo: 1, RewardSnapshotId: "rank-snapshot", RewardSnapshotHash: strings.Repeat("c", 64), RewardName: "Champion", RewardItems: []*activityv1.ActivityTemplateRewardItem{item}}},
})
job := rewardJobFromProto(&activityv1.ActivityTemplateRewardJob{
RewardJobId: "job-1", RewardSnapshotId: "rank-snapshot", RewardSnapshotHash: strings.Repeat("c", 64), RewardName: "Champion", RewardItems: []*activityv1.ActivityTemplateRewardItem{item},
}, nil)
if len(template.Tasks) != 1 || template.Tasks[0].RewardSnapshotHash != strings.Repeat("b", 64) || len(template.Tasks[0].RewardItems) != 1 ||
len(template.RankRewards) != 1 || template.RankRewards[0].RewardSnapshotHash != strings.Repeat("c", 64) || len(template.RankRewards[0].RewardItems) != 1 ||
job.RewardSnapshotID != "rank-snapshot" || job.RewardSnapshotHash != strings.Repeat("c", 64) || len(job.RewardItems) != 1 {
t.Fatalf("published reward snapshots were dropped: template=%+v job=%+v", template, job)
}
}
func TestLeaderboardProfileFailureDoesNotHideRankFacts(t *testing.T) {
gin.SetMode(gin.TestMode)
owner := activityTemplateDataOwnerClient{leaderboard: &activityv1.AdminListActivityTemplateLeaderboardResponse{
Period: &activityv1.ActivityTemplateRankPeriod{TemplateId: "acttpl_1", BoardType: "total", PeriodKey: "total"},
Entries: []*activityv1.ActivityTemplateLeaderboardEntry{{RankNo: 1, UserId: 501, Score: 900}}, Total: 1,
}}
users := &captureActivityTemplateUsers{err: errors.New("user service unavailable")}
handler := New(&owner, nil).BindUserBatchGetter(users)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/activities/templates/acttpl_1/leaderboard?version_no=1&board_type=total", nil)
ctx.Params = gin.Params{{Key: "template_id", Value: "acttpl_1"}}
handler.Leaderboard(ctx)
if recorder.Code != http.StatusOK || users.calls != 1 || !strings.Contains(recorder.Body.String(), `"score":900`) || strings.Contains(recorder.Body.String(), `"user":`) {
t.Fatalf("profile degradation hid or changed rank facts: status=%d body=%s", recorder.Code, recorder.Body.String())
}
}

View File

@ -0,0 +1,130 @@
package activitytemplate
import (
"strings"
activityv1 "hyapp.local/api/proto/activity/v1"
)
type localeRequest struct {
Locale string `json:"locale"`
Title string `json:"title"`
Rules string `json:"rules"`
}
type giftRequest struct {
GiftID string `json:"gift_id"`
SortOrder int32 `json:"sort_order"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
CoinPrice int64 `json:"coin_price"`
PriceVersion string `json:"price_version"`
}
type taskRequest struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TargetValue int64 `json:"target_value"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
SortOrder int32 `json:"sort_order"`
}
type rankRewardRequest struct {
BoardType string `json:"board_type"`
RankFrom int32 `json:"rank_from"`
RankTo int32 `json:"rank_to"`
ResourceGroupID int64 `json:"resource_group_id"`
SortOrder int32 `json:"sort_order"`
}
type assetRequest struct {
AssetKey string `json:"asset_key"`
Locale string `json:"locale"`
URL string `json:"url"`
MediaType string `json:"media_type"`
SortOrder int32 `json:"sort_order"`
}
// templateRequest 与 HTTP JSON 一一对应;不在 admin 层重复业务校验,确保保存和发布都复用 activity-service 的同一规则。
type templateRequest struct {
TemplateCode string `json:"template_code"`
Name string `json:"name"`
ActivityType string `json:"activity_type"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
AllRegions bool `json:"all_regions"`
RegionIDs []int64 `json:"region_ids"`
Locales []localeRequest `json:"locales"`
Gifts []giftRequest `json:"gifts"`
Tasks []taskRequest `json:"tasks"`
DailyLeaderboardSize int32 `json:"daily_leaderboard_size"`
TotalLeaderboardSize int32 `json:"total_leaderboard_size"`
RankRewards []rankRewardRequest `json:"rank_rewards"`
Assets []assetRequest `json:"assets"`
DisplayConfigJSON string `json:"display_config_json"`
ExpectedRevision int64 `json:"expected_revision,omitempty"`
}
type statusRequest struct {
Status string `json:"status"`
ExpectedRevision int64 `json:"expected_revision"`
}
type cloneRequest struct {
SourceVersion int64 `json:"source_version"`
TemplateCode string `json:"template_code"`
Name string `json:"name"`
}
type validateRequest struct {
Template templateRequest `json:"template"`
ForPublish bool `json:"for_publish"`
}
func (r templateRequest) toProto(templateID string) *activityv1.ActivityTemplate {
item := &activityv1.ActivityTemplate{
TemplateId: strings.TrimSpace(templateID),
TemplateCode: strings.TrimSpace(r.TemplateCode),
Name: strings.TrimSpace(r.Name),
ActivityType: strings.TrimSpace(r.ActivityType),
StartMs: r.StartMS,
EndMs: r.EndMS,
AllRegions: r.AllRegions,
RegionIds: append([]int64(nil), r.RegionIDs...),
Locales: make([]*activityv1.ActivityTemplateLocale, 0, len(r.Locales)),
Gifts: make([]*activityv1.ActivityTemplateGift, 0, len(r.Gifts)),
Tasks: make([]*activityv1.ActivityTemplateTask, 0, len(r.Tasks)),
DailyLeaderboardSize: r.DailyLeaderboardSize,
TotalLeaderboardSize: r.TotalLeaderboardSize,
RankRewards: make([]*activityv1.ActivityTemplateRankReward, 0, len(r.RankRewards)),
Assets: make([]*activityv1.ActivityTemplateAsset, 0, len(r.Assets)),
DisplayConfigJson: strings.TrimSpace(r.DisplayConfigJSON),
}
for _, locale := range r.Locales {
item.Locales = append(item.Locales, &activityv1.ActivityTemplateLocale{Locale: locale.Locale, Title: locale.Title, Rules: locale.Rules})
}
for _, gift := range r.Gifts {
item.Gifts = append(item.Gifts, &activityv1.ActivityTemplateGift{
GiftId: gift.GiftID, SortOrder: gift.SortOrder, Name: gift.Name, IconUrl: gift.IconURL,
CoinPrice: gift.CoinPrice, PriceVersion: gift.PriceVersion,
})
}
for _, task := range r.Tasks {
item.Tasks = append(item.Tasks, &activityv1.ActivityTemplateTask{
TaskKey: task.TaskKey, TaskType: task.TaskType, TargetValue: task.TargetValue,
RewardResourceGroupId: task.RewardResourceGroupID, SortOrder: task.SortOrder,
})
}
for _, reward := range r.RankRewards {
item.RankRewards = append(item.RankRewards, &activityv1.ActivityTemplateRankReward{
BoardType: reward.BoardType, RankFrom: reward.RankFrom, RankTo: reward.RankTo,
ResourceGroupId: reward.ResourceGroupID, SortOrder: reward.SortOrder,
})
}
for _, asset := range r.Assets {
item.Assets = append(item.Assets, &activityv1.ActivityTemplateAsset{
AssetKey: asset.AssetKey, Locale: asset.Locale, Url: asset.URL, MediaType: asset.MediaType, SortOrder: asset.SortOrder,
})
}
return item
}

View File

@ -0,0 +1,382 @@
package activitytemplate
import (
"hyapp-admin-server/internal/integration/userclient"
activityv1 "hyapp.local/api/proto/activity/v1"
)
type localeDTO struct {
Locale string `json:"locale"`
Title string `json:"title"`
Rules string `json:"rules"`
}
type giftDTO struct {
GiftID string `json:"gift_id"`
SortOrder int32 `json:"sort_order"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
CoinPrice int64 `json:"coin_price"`
PriceVersion string `json:"price_version"`
}
// rewardItemDTO is the immutable, display-safe reward item captured at publish
// time. Admin reads this snapshot instead of joining mutable wallet catalog rows.
type rewardItemDTO struct {
ItemType string `json:"item_type"`
ResourceID int64 `json:"resource_id"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
WalletAssetType string `json:"wallet_asset_type"`
WalletAssetAmount int64 `json:"wallet_asset_amount"`
SortOrder int32 `json:"sort_order"`
}
type taskDTO struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TargetValue int64 `json:"target_value"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardSnapshotHash string `json:"reward_snapshot_hash"`
RewardName string `json:"reward_name"`
RewardItems []rewardItemDTO `json:"reward_items"`
}
type rankRewardDTO struct {
BoardType string `json:"board_type"`
RankFrom int32 `json:"rank_from"`
RankTo int32 `json:"rank_to"`
ResourceGroupID int64 `json:"resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardSnapshotHash string `json:"reward_snapshot_hash"`
RewardName string `json:"reward_name"`
RewardItems []rewardItemDTO `json:"reward_items"`
}
type assetDTO struct {
AssetKey string `json:"asset_key"`
Locale string `json:"locale"`
URL string `json:"url"`
MediaType string `json:"media_type"`
SortOrder int32 `json:"sort_order"`
}
type templateSummaryDTO struct {
AppCode string `json:"app_code"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
Name string `json:"name"`
ActivityType string `json:"activity_type"`
Status string `json:"status"`
LifecycleStatus string `json:"lifecycle_status"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
AllRegions bool `json:"all_regions"`
RegionIDs []int64 `json:"region_ids"`
Revision int64 `json:"revision"`
PublishedVersion int64 `json:"published_version"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type templateDTO struct {
AppCode string `json:"app_code"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
Name string `json:"name"`
ActivityType string `json:"activity_type"`
Status string `json:"status"`
LifecycleStatus string `json:"lifecycle_status"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
AllRegions bool `json:"all_regions"`
RegionIDs []int64 `json:"region_ids"`
Locales []localeDTO `json:"locales"`
Gifts []giftDTO `json:"gifts"`
Tasks []taskDTO `json:"tasks"`
DailyLeaderboardSize int32 `json:"daily_leaderboard_size"`
TotalLeaderboardSize int32 `json:"total_leaderboard_size"`
RankRewards []rankRewardDTO `json:"rank_rewards"`
Assets []assetDTO `json:"assets"`
DisplayConfigJSON string `json:"display_config_json"`
Revision int64 `json:"revision"`
PublishedVersion int64 `json:"published_version"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
PublishedByAdminID int64 `json:"published_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
PublishedAtMS int64 `json:"published_at_ms"`
ArchivedAtMS int64 `json:"archived_at_ms"`
}
type versionDTO struct {
TemplateID string `json:"template_id"`
VersionNo int64 `json:"version_no"`
Snapshot templateDTO `json:"snapshot"`
PublishedByAdminID int64 `json:"published_by_admin_id"`
PublishedAtMS int64 `json:"published_at_ms"`
RuntimeFromMS int64 `json:"runtime_from_ms"`
RuntimeToMS int64 `json:"runtime_to_ms"`
}
type validationIssueDTO struct {
Field string `json:"field"`
Code string `json:"code"`
Message string `json:"message"`
}
type pageDTO struct {
Items any `json:"items"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
ServerTimeMS int64 `json:"server_time_ms,omitempty"`
}
type rankPeriodDTO struct {
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
VersionNo int64 `json:"version_no"`
BoardType string `json:"board_type"`
PeriodKey string `json:"period_key"`
PeriodStartMS int64 `json:"period_start_ms"`
PeriodEndMS int64 `json:"period_end_ms"`
SettleAfterMS int64 `json:"settle_after_ms"`
Status string `json:"status"`
SettledAtMS int64 `json:"settled_at_ms"`
}
type leaderboardEntryDTO struct {
RankNo int32 `json:"rank_no"`
UserID int64 `json:"user_id"`
Score int64 `json:"score"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
FirstScoredAtMS int64 `json:"first_scored_at_ms"`
Snapshot bool `json:"snapshot"`
User *activityTemplateUserDTO `json:"user,omitempty"`
}
type leaderboardPageDTO struct {
Period rankPeriodDTO `json:"period"`
Items []leaderboardEntryDTO `json:"items"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
}
type rewardJobDTO struct {
RewardJobID string `json:"reward_job_id"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
VersionNo int64 `json:"version_no"`
BoardType string `json:"board_type"`
PeriodKey string `json:"period_key"`
RankNo int32 `json:"rank_no"`
UserID int64 `json:"user_id"`
Score int64 `json:"score"`
ResourceGroupID int64 `json:"resource_group_id"`
WalletCommandID string `json:"wallet_command_id"`
WalletGrantID string `json:"wallet_grant_id"`
Status string `json:"status"`
FailureReason string `json:"failure_reason"`
AttemptCount int32 `json:"attempt_count"`
MaxAttempts int32 `json:"max_attempts"`
NextRetryAtMS int64 `json:"next_retry_at_ms"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
GrantedAtMS int64 `json:"granted_at_ms"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardSnapshotHash string `json:"reward_snapshot_hash"`
RewardName string `json:"reward_name"`
RewardItems []rewardItemDTO `json:"reward_items"`
User *activityTemplateUserDTO `json:"user,omitempty"`
}
type taskClaimDTO struct {
ClaimID string `json:"claim_id"`
CommandID string `json:"command_id"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
VersionNo int64 `json:"version_no"`
TaskDay string `json:"task_day"`
TaskKey string `json:"task_key"`
UserID int64 `json:"user_id"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
WalletCommandID string `json:"wallet_command_id"`
WalletGrantID string `json:"wallet_grant_id"`
Status string `json:"status"`
FailureReason string `json:"failure_reason"`
AttemptCount int32 `json:"attempt_count"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
GrantedAtMS int64 `json:"granted_at_ms"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardSnapshotHash string `json:"reward_snapshot_hash"`
RewardName string `json:"reward_name"`
RewardItems []rewardItemDTO `json:"reward_items"`
EarningRegionID int64 `json:"earning_region_id"`
AttributionAtMS int64 `json:"attribution_at_ms"`
User *activityTemplateUserDTO `json:"user,omitempty"`
}
type activityTemplateUserDTO struct {
DisplayUserID string `json:"display_user_id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
}
func rankPeriodFromProto(item *activityv1.ActivityTemplateRankPeriod) rankPeriodDTO {
if item == nil {
return rankPeriodDTO{}
}
return rankPeriodDTO{
TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(), VersionNo: item.GetVersionNo(),
BoardType: item.GetBoardType(), PeriodKey: item.GetPeriodKey(), PeriodStartMS: item.GetPeriodStartMs(),
PeriodEndMS: item.GetPeriodEndMs(), SettleAfterMS: item.GetSettleAfterMs(),
Status: item.GetStatus(), SettledAtMS: item.GetSettledAtMs(),
}
}
func leaderboardEntryFromProto(item *activityv1.ActivityTemplateLeaderboardEntry, users map[int64]*userclient.User) leaderboardEntryDTO {
if item == nil {
return leaderboardEntryDTO{}
}
out := leaderboardEntryDTO{
RankNo: item.GetRankNo(), UserID: item.GetUserId(), Score: item.GetScore(), GiftCount: item.GetGiftCount(),
CoinAmount: item.GetCoinAmount(), FirstScoredAtMS: item.GetFirstScoredAtMs(), Snapshot: item.GetSnapshot(),
}
out.User = activityTemplateUserFromClient(users[item.GetUserId()])
return out
}
func rewardJobFromProto(item *activityv1.ActivityTemplateRewardJob, users map[int64]*userclient.User) rewardJobDTO {
if item == nil {
return rewardJobDTO{}
}
out := rewardJobDTO{
RewardJobID: item.GetRewardJobId(), TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(),
VersionNo: item.GetVersionNo(), BoardType: item.GetBoardType(), PeriodKey: item.GetPeriodKey(), RankNo: item.GetRankNo(),
UserID: item.GetUserId(), Score: item.GetScore(), ResourceGroupID: item.GetResourceGroupId(),
WalletCommandID: item.GetWalletCommandId(), WalletGrantID: item.GetWalletGrantId(), Status: item.GetStatus(),
FailureReason: item.GetFailureReason(), AttemptCount: item.GetAttemptCount(), MaxAttempts: item.GetMaxAttempts(),
NextRetryAtMS: item.GetNextRetryAtMs(), CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(), GrantedAtMS: item.GetGrantedAtMs(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardSnapshotHash: item.GetRewardSnapshotHash(),
RewardName: item.GetRewardName(), RewardItems: rewardItemsFromProto(item.GetRewardItems()),
}
out.User = activityTemplateUserFromClient(users[item.GetUserId()])
return out
}
func taskClaimFromProto(item *activityv1.ActivityTemplateTaskClaim, users map[int64]*userclient.User) taskClaimDTO {
if item == nil {
return taskClaimDTO{RewardItems: []rewardItemDTO{}}
}
out := taskClaimDTO{
ClaimID: item.GetClaimId(), CommandID: item.GetCommandId(), TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(),
VersionNo: item.GetVersionNo(), TaskDay: item.GetTaskDay(), TaskKey: item.GetTaskKey(), UserID: item.GetUserId(),
RewardResourceGroupID: item.GetRewardResourceGroupId(), WalletCommandID: item.GetWalletCommandId(), WalletGrantID: item.GetWalletGrantId(),
Status: item.GetStatus(), FailureReason: item.GetFailureReason(), AttemptCount: item.GetAttemptCount(),
CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), GrantedAtMS: item.GetGrantedAtMs(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardSnapshotHash: item.GetRewardSnapshotHash(), RewardName: item.GetRewardName(),
RewardItems: rewardItemsFromProto(item.GetRewardItems()), EarningRegionID: item.GetEarningRegionId(), AttributionAtMS: item.GetAttributionAtMs(),
}
out.User = activityTemplateUserFromClient(users[item.GetUserId()])
return out
}
func rewardItemsFromProto(items []*activityv1.ActivityTemplateRewardItem) []rewardItemDTO {
out := make([]rewardItemDTO, 0, len(items))
for _, item := range items {
if item == nil {
continue
}
out = append(out, rewardItemDTO{
ItemType: item.GetItemType(), ResourceID: item.GetResourceId(), ResourceType: item.GetResourceType(),
Name: item.GetName(), IconURL: item.GetIconUrl(), Quantity: item.GetQuantity(), DurationMS: item.GetDurationMs(),
WalletAssetType: item.GetWalletAssetType(), WalletAssetAmount: item.GetWalletAssetAmount(), SortOrder: item.GetSortOrder(),
})
}
return out
}
func activityTemplateUserFromClient(item *userclient.User) *activityTemplateUserDTO {
if item == nil {
return nil
}
return &activityTemplateUserDTO{DisplayUserID: item.DisplayUserID, Username: item.Username, Avatar: item.Avatar}
}
func summaryFromProto(item *activityv1.ActivityTemplateSummary) templateSummaryDTO {
if item == nil {
return templateSummaryDTO{RegionIDs: []int64{}}
}
return templateSummaryDTO{
AppCode: item.GetAppCode(), TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(),
Name: item.GetName(), ActivityType: item.GetActivityType(), Status: item.GetStatus(), LifecycleStatus: item.GetLifecycleStatus(),
StartMS: item.GetStartMs(), EndMS: item.GetEndMs(), AllRegions: item.GetAllRegions(), RegionIDs: cloneInt64Slice(item.GetRegionIds()),
Revision: item.GetRevision(), PublishedVersion: item.GetPublishedVersion(),
CreatedByAdminID: item.GetCreatedByAdminId(), UpdatedByAdminID: item.GetUpdatedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
func templateFromProto(item *activityv1.ActivityTemplate) templateDTO {
if item == nil {
return templateDTO{RegionIDs: []int64{}, Locales: []localeDTO{}, Gifts: []giftDTO{}, Tasks: []taskDTO{}, RankRewards: []rankRewardDTO{}, Assets: []assetDTO{}}
}
out := templateDTO{
AppCode: item.GetAppCode(), TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(), Name: item.GetName(),
ActivityType: item.GetActivityType(), Status: item.GetStatus(), LifecycleStatus: item.GetLifecycleStatus(),
StartMS: item.GetStartMs(), EndMS: item.GetEndMs(), AllRegions: item.GetAllRegions(), RegionIDs: cloneInt64Slice(item.GetRegionIds()),
Locales: make([]localeDTO, 0, len(item.GetLocales())), Gifts: make([]giftDTO, 0, len(item.GetGifts())),
Tasks: make([]taskDTO, 0, len(item.GetTasks())), RankRewards: make([]rankRewardDTO, 0, len(item.GetRankRewards())),
Assets: make([]assetDTO, 0, len(item.GetAssets())), DailyLeaderboardSize: item.GetDailyLeaderboardSize(),
TotalLeaderboardSize: item.GetTotalLeaderboardSize(), DisplayConfigJSON: item.GetDisplayConfigJson(),
Revision: item.GetRevision(), PublishedVersion: item.GetPublishedVersion(),
CreatedByAdminID: item.GetCreatedByAdminId(), UpdatedByAdminID: item.GetUpdatedByAdminId(), PublishedByAdminID: item.GetPublishedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), PublishedAtMS: item.GetPublishedAtMs(), ArchivedAtMS: item.GetArchivedAtMs(),
}
for _, locale := range item.GetLocales() {
out.Locales = append(out.Locales, localeDTO{Locale: locale.GetLocale(), Title: locale.GetTitle(), Rules: locale.GetRules()})
}
for _, gift := range item.GetGifts() {
out.Gifts = append(out.Gifts, giftDTO{GiftID: gift.GetGiftId(), SortOrder: gift.GetSortOrder(), Name: gift.GetName(), IconURL: gift.GetIconUrl(), CoinPrice: gift.GetCoinPrice(), PriceVersion: gift.GetPriceVersion()})
}
for _, task := range item.GetTasks() {
out.Tasks = append(out.Tasks, taskDTO{
TaskKey: task.GetTaskKey(), TaskType: task.GetTaskType(), TargetValue: task.GetTargetValue(),
RewardResourceGroupID: task.GetRewardResourceGroupId(), SortOrder: task.GetSortOrder(),
RewardSnapshotID: task.GetRewardSnapshotId(), RewardSnapshotHash: task.GetRewardSnapshotHash(),
RewardName: task.GetRewardName(), RewardItems: rewardItemsFromProto(task.GetRewardItems()),
})
}
for _, reward := range item.GetRankRewards() {
out.RankRewards = append(out.RankRewards, rankRewardDTO{
BoardType: reward.GetBoardType(), RankFrom: reward.GetRankFrom(), RankTo: reward.GetRankTo(),
ResourceGroupID: reward.GetResourceGroupId(), SortOrder: reward.GetSortOrder(),
RewardSnapshotID: reward.GetRewardSnapshotId(), RewardSnapshotHash: reward.GetRewardSnapshotHash(),
RewardName: reward.GetRewardName(), RewardItems: rewardItemsFromProto(reward.GetRewardItems()),
})
}
for _, asset := range item.GetAssets() {
out.Assets = append(out.Assets, assetDTO{AssetKey: asset.GetAssetKey(), Locale: asset.GetLocale(), URL: asset.GetUrl(), MediaType: asset.GetMediaType(), SortOrder: asset.GetSortOrder()})
}
return out
}
func cloneInt64Slice(items []int64) []int64 {
out := make([]int64, len(items))
copy(out, items)
return out
}

View File

@ -0,0 +1,30 @@
package activitytemplate
import (
"hyapp-admin-server/internal/middleware"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
if h == nil {
return
}
protected.GET("/admin/activities/templates", middleware.RequirePermission("activity-template:view"), h.List)
protected.POST("/admin/activities/templates", middleware.RequirePermission("activity-template:create"), h.Create)
protected.POST("/admin/activities/templates/validate", middleware.RequireAnyPermission("activity-template:create", "activity-template:update", "activity-template:publish"), h.Validate)
protected.GET("/admin/activities/templates/:template_id", middleware.RequirePermission("activity-template:view"), h.Get)
protected.PUT("/admin/activities/templates/:template_id", middleware.RequirePermission("activity-template:update"), h.Update)
protected.PATCH("/admin/activities/templates/:template_id/status", middleware.RequirePermission("activity-template:publish"), h.SetStatus)
protected.DELETE("/admin/activities/templates/:template_id", middleware.RequirePermission("activity-template:delete"), h.Delete)
protected.GET("/admin/activities/templates/:template_id/versions", middleware.RequirePermission("activity-template:view"), h.Versions)
protected.POST("/admin/activities/templates/:template_id/clone", middleware.RequirePermission("activity-template:create"), h.Clone)
protected.GET("/admin/activities/templates/:template_id/leaderboard", middleware.RequirePermission("activity-template:data"), h.Leaderboard)
protected.GET("/admin/activities/templates/:template_id/data", middleware.RequirePermission("activity-template:data"), h.Data)
// 导出复用同一受限聚合契约,前端按 overview/trend/gifts/tasks 分区生成 CSV避免另开一条可能绕过 366 天和 Top100 限制的数据链路。
protected.GET("/admin/activities/templates/:template_id/data/export", middleware.RequirePermission("activity-template:export"), h.Data)
protected.GET("/admin/activities/templates/:template_id/rewards", middleware.RequirePermission("activity-template:data"), h.RewardJobs)
protected.POST("/admin/activities/templates/:template_id/rewards/:reward_job_id/retry", middleware.RequirePermission("activity-template:retry"), h.RetryRewardJob)
protected.GET("/admin/activities/templates/:template_id/task-claims", middleware.RequirePermission("activity-template:data"), h.TaskClaims)
protected.POST("/admin/activities/templates/:template_id/task-claims/:claim_id/retry", middleware.RequirePermission("activity-template:retry"), h.RetryTaskClaim)
}

View File

@ -0,0 +1,92 @@
package activitytemplate
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
type validatePermissionClient struct {
activityclient.Client
}
func (validatePermissionClient) ValidateActivityTemplate(context.Context, *activityv1.ValidateActivityTemplateRequest) (*activityv1.ValidateActivityTemplateResponse, error) {
return &activityv1.ValidateActivityTemplateResponse{Valid: true}, nil
}
func TestRegisterRoutesExposesActivityTemplateContract(t *testing.T) {
gin.SetMode(gin.TestMode)
engine := gin.New()
RegisterRoutes(engine.Group("/api/v1"), &Handler{})
actual := make(map[string]struct{})
for _, route := range engine.Routes() {
actual[route.Method+" "+route.Path] = struct{}{}
}
expected := []string{
"GET /api/v1/admin/activities/templates",
"POST /api/v1/admin/activities/templates",
"POST /api/v1/admin/activities/templates/validate",
"GET /api/v1/admin/activities/templates/:template_id",
"PUT /api/v1/admin/activities/templates/:template_id",
"PATCH /api/v1/admin/activities/templates/:template_id/status",
"DELETE /api/v1/admin/activities/templates/:template_id",
"GET /api/v1/admin/activities/templates/:template_id/versions",
"POST /api/v1/admin/activities/templates/:template_id/clone",
"GET /api/v1/admin/activities/templates/:template_id/leaderboard",
"GET /api/v1/admin/activities/templates/:template_id/data",
"GET /api/v1/admin/activities/templates/:template_id/data/export",
"GET /api/v1/admin/activities/templates/:template_id/rewards",
"POST /api/v1/admin/activities/templates/:template_id/rewards/:reward_job_id/retry",
"GET /api/v1/admin/activities/templates/:template_id/task-claims",
"POST /api/v1/admin/activities/templates/:template_id/task-claims/:claim_id/retry",
}
for _, route := range expected {
if _, ok := actual[route]; !ok {
t.Errorf("missing route %s; actual=%v", route, actual)
}
}
}
func TestValidateRouteAcceptsCreateUpdateOrPublishPermission(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
permission string
wantStatus int
}{
{permission: "activity-template:create", wantStatus: http.StatusOK},
{permission: "activity-template:update", wantStatus: http.StatusOK},
{permission: "activity-template:publish", wantStatus: http.StatusOK},
{permission: "activity-template:view", wantStatus: http.StatusForbidden},
}
for _, tt := range tests {
t.Run(tt.permission, func(t *testing.T) {
engine := gin.New()
engine.Use(func(c *gin.Context) {
c.Set(middleware.ContextPermissions, []string{tt.permission})
c.Next()
})
RegisterRoutes(engine.Group("/api/v1"), New(validatePermissionClient{}, nil))
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/api/v1/admin/activities/templates/validate", strings.NewReader(`{
"template":{"template_code":"gift_challenge_2026","name":"Gift Challenge"},
"for_publish":false
}`))
request.Header.Set("Content-Type", "application/json")
engine.ServeHTTP(recorder, request)
if recorder.Code != tt.wantStatus {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, tt.wantStatus, recorder.Body.String())
}
})
}
}

View File

@ -18,7 +18,8 @@ func TestH5AppScopeSchemaMigrationCreatesScopedKeys(t *testing.T) {
for _, snippet := range []string{ for _, snippet := range []string{
"app_code VARCHAR(32) NOT NULL DEFAULT ''", "app_code VARCHAR(32) NOT NULL DEFAULT ''",
"is_deleted BOOLEAN NOT NULL DEFAULT FALSE", "is_deleted BOOLEAN NOT NULL DEFAULT FALSE",
"DROP INDEX uk_admin_app_configs_group_key", "INDEX_NAME = 'uk_admin_app_configs_group_key'",
"INDEX_NAME = 'idx_admin_app_config_group_key'",
"uk_admin_app_config_app_group_key (app_code, `group`, `key`)", "uk_admin_app_config_app_group_key (app_code, `group`, `key`)",
} { } {
if !strings.Contains(sqlText, snippet) { if !strings.Contains(sqlText, snippet) {

View File

@ -124,12 +124,15 @@ var (
} }
activityRead = []string{ activityRead = []string{
"activity-template:view", "activity-template:data",
"daily-task:view", "first-recharge-reward:view", "cumulative-recharge-reward:view", "daily-task:view", "first-recharge-reward:view", "cumulative-recharge-reward:view",
"invite-activity-reward:view", "seven-day-checkin:view", "room-rocket:view", "room-turnover-reward:view", "invite-activity-reward:view", "seven-day-checkin:view", "room-rocket:view", "room-turnover-reward:view",
"wheel:view", "weekly-star:view", "agency-opening:view", "user-leaderboard:view", "red-packet:view", "wheel:view", "weekly-star:view", "agency-opening:view", "user-leaderboard:view", "red-packet:view",
"cp-config:view", "cp-weekly-rank:view", "vip-config:view", "achievement:view", "cp-config:view", "cp-weekly-rank:view", "vip-config:view", "achievement:view",
} }
activityManage = []string{ activityManage = []string{
"activity-template:create", "activity-template:update", "activity-template:publish", "activity-template:delete",
"activity-template:export", "activity-template:retry",
"daily-task:create", "daily-task:update", "daily-task:status", "daily-task:create", "daily-task:update", "daily-task:status",
"first-recharge-reward:update", "cumulative-recharge-reward:update", "invite-activity-reward:update", "first-recharge-reward:update", "cumulative-recharge-reward:update", "invite-activity-reward:update",
"seven-day-checkin:update", "room-rocket:update", "room-turnover-reward:update", "room-turnover-reward:retry", "seven-day-checkin:update", "room-rocket:update", "room-turnover-reward:update", "room-turnover-reward:retry",

View File

@ -173,6 +173,14 @@ var defaultPermissions = []model.Permission{
{Name: "每日任务创建", Code: "daily-task:create", Kind: "button"}, {Name: "每日任务创建", Code: "daily-task:create", Kind: "button"},
{Name: "每日任务更新", Code: "daily-task:update", Kind: "button"}, {Name: "每日任务更新", Code: "daily-task:update", Kind: "button"},
{Name: "每日任务状态", Code: "daily-task:status", Kind: "button"}, {Name: "每日任务状态", Code: "daily-task:status", Kind: "button"},
{Name: "活动模版查看", Code: "activity-template:view", Kind: "menu"},
{Name: "活动模版创建", Code: "activity-template:create", Kind: "button"},
{Name: "活动模版更新", Code: "activity-template:update", Kind: "button"},
{Name: "活动模版发布", Code: "activity-template:publish", Kind: "button"},
{Name: "活动模版删除", Code: "activity-template:delete", Kind: "button"},
{Name: "活动模版数据", Code: "activity-template:data", Kind: "button"},
{Name: "活动模版导出", Code: "activity-template:export", Kind: "button"},
{Name: "活动模版重试", Code: "activity-template:retry", Kind: "button"},
{Name: "成就配置查看", Code: "achievement:view", Kind: "menu"}, {Name: "成就配置查看", Code: "achievement:view", Kind: "menu"},
{Name: "成就配置创建", Code: "achievement:create", Kind: "button"}, {Name: "成就配置创建", Code: "achievement:create", Kind: "button"},
{Name: "成就配置更新", Code: "achievement:update", Kind: "button"}, {Name: "成就配置更新", Code: "achievement:update", Kind: "button"},
@ -270,7 +278,7 @@ func (s *Store) seedMenus() error {
{Title: "资源管理", Code: "resources", Path: "", Icon: "inventory", PermissionCode: "", Sort: 60, Visible: true}, {Title: "资源管理", Code: "resources", Path: "", Icon: "inventory", PermissionCode: "", Sort: 60, Visible: true},
{Title: "运营管理", Code: "operations", Path: "", Icon: "operations", PermissionCode: "", Sort: 70, Visible: true}, {Title: "运营管理", Code: "operations", Path: "", Icon: "operations", PermissionCode: "", Sort: 70, Visible: true},
{Title: "支付管理", Code: "payment", Path: "", Icon: "wallet", PermissionCode: "", Sort: 80, Visible: true}, {Title: "支付管理", Code: "payment", Path: "", Icon: "wallet", PermissionCode: "", Sort: 80, Visible: true},
{Title: "活动管理", Code: "activities", Path: "", Icon: "campaign", PermissionCode: "", Sort: 90, Visible: true}, {Title: "运营活动", Code: "activities", Path: "", Icon: "campaign", PermissionCode: "", Sort: 90, Visible: true},
{Title: "游戏管理", Code: "games", Path: "", Icon: "sports_esports", PermissionCode: "", Sort: 100, Visible: true}, {Title: "游戏管理", Code: "games", Path: "", Icon: "sports_esports", PermissionCode: "", Sort: 100, Visible: true},
{Title: "地区管理", Code: "geo", Path: "", Icon: "map", PermissionCode: "", Sort: 110, Visible: true}, {Title: "地区管理", Code: "geo", Path: "", Icon: "map", PermissionCode: "", Sort: 110, Visible: true},
{Title: "后台设置", Code: "system", Path: "", Icon: "settings", PermissionCode: "", Sort: 120, Visible: true}, {Title: "后台设置", Code: "system", Path: "", Icon: "settings", PermissionCode: "", Sort: 120, Visible: true},
@ -361,6 +369,7 @@ func (s *Store) seedMenus() error {
{ParentID: &paymentID, Title: "三方临时支付链接", Code: "payment-temporary-links", Path: "/payment/temporary-links", Icon: "receipt", PermissionCode: "payment-temporary-link:view", Sort: 70, Visible: true}, {ParentID: &paymentID, Title: "三方临时支付链接", Code: "payment-temporary-links", Path: "/payment/temporary-links", Icon: "receipt", PermissionCode: "payment-temporary-link:view", Sort: 70, Visible: true},
{ParentID: &paymentID, Title: "支付内购商品", Code: "payment-recharge-products", Path: "/payment/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 71, Visible: true}, {ParentID: &paymentID, Title: "支付内购商品", Code: "payment-recharge-products", Path: "/payment/recharge-products", Icon: "wallet", PermissionCode: "payment-product:view", Sort: 71, Visible: true},
{ParentID: &paymentID, Title: "币商充值订单", Code: "finance-coin-seller-recharge-orders", Path: "/finance/orders/coin-seller-recharges", Icon: "receipt", PermissionCode: "finance-order:coin-seller-recharge:view", Sort: 72, Visible: true}, {ParentID: &paymentID, Title: "币商充值订单", Code: "finance-coin-seller-recharge-orders", Path: "/finance/orders/coin-seller-recharges", Icon: "receipt", PermissionCode: "finance-order:coin-seller-recharge:view", Sort: 72, Visible: true},
{ParentID: &activityID, Title: "活动模版", Code: "activity-template", Path: "/activities/templates", Icon: "campaign", PermissionCode: "activity-template:view", Sort: 68, Visible: true},
{ParentID: &activityID, Title: "每日任务", Code: "daily-task-list", Path: "/activities/daily-tasks", Icon: "task", PermissionCode: "daily-task:view", Sort: 69, Visible: true}, {ParentID: &activityID, Title: "每日任务", Code: "daily-task-list", Path: "/activities/daily-tasks", Icon: "task", PermissionCode: "daily-task:view", Sort: 69, Visible: true},
{ParentID: &activityID, Title: "注册奖励", Code: "registration-reward", Path: "/activities/registration-reward", Icon: "gift", PermissionCode: "registration-reward:view", Sort: 70, Visible: true}, {ParentID: &activityID, Title: "注册奖励", Code: "registration-reward", Path: "/activities/registration-reward", Icon: "gift", PermissionCode: "registration-reward:view", Sort: 70, Visible: true},
{ParentID: &activityID, Title: "成就配置", Code: "achievement-config", Path: "/activities/achievements", Icon: "military_tech", PermissionCode: "achievement:view", Sort: 71, Visible: true}, {ParentID: &activityID, Title: "成就配置", Code: "achievement-config", Path: "/activities/achievements", Icon: "military_tech", PermissionCode: "achievement:view", Sort: 71, Visible: true},

View File

@ -201,6 +201,26 @@ func TestGiftRecordPermissionMigrationExtendsOperationsReadRoles(t *testing.T) {
} }
} }
func TestActivityTemplatePermissionMigrationExtendsManagedRoles(t *testing.T) {
content, err := os.ReadFile("../../migrations/096_activity_template_navigation.sql")
if err != nil {
t.Fatalf("read activity template permission migration: %v", err)
}
sqlText := string(content)
for _, token := range []string{
"'活动模版'", "'activity-template'", "'/activities/templates'",
"'platform-admin', 'ops-admin', 'product-lead'",
"'operations-specialist', 'product-specialist', 'auditor', 'readonly'",
"'activity-template:view'", "'activity-template:create'", "'activity-template:update'",
"'activity-template:publish'", "'activity-template:delete'", "'activity-template:data'",
"'activity-template:export'", "'activity-template:retry'",
} {
if !strings.Contains(sqlText, token) {
t.Fatalf("activity template migration missing %s", token)
}
}
}
func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList string) { func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList string) {
t.Helper() t.Helper()
quotedCode := regexp.MustCompile(`'([a-z0-9:-]+)'`) quotedCode := regexp.MustCompile(`'([a-z0-9:-]+)'`)
@ -210,9 +230,13 @@ func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList strin
actual = append(actual, match[1]) actual = append(actual, match[1])
} }
expected := append([]string(nil), defaultRolePermissionCodes(roleCode)...) expected := append([]string(nil), defaultRolePermissionCodes(roleCode)...)
// 093 已在生产执行不能为了新增页面回写旧迁移095 以增量方式给同一批运营只读岗位补 gift-record:view。 // 093 已在生产执行不能为了新增页面回写旧迁移095/096 以增量方式补充页面权限。
// 这里仍校验 093 的原始精确矩阵,新权限由上面的 095 专项测试锁定。 // 这里仍校验 093 的原始精确矩阵,新权限由上面的专项迁移测试锁定。
expected = stringSetDifference(expected, []string{"gift-record:view"}) expected = stringSetDifference(expected, []string{
"gift-record:view",
"activity-template:view", "activity-template:create", "activity-template:update", "activity-template:publish",
"activity-template:delete", "activity-template:data", "activity-template:export", "activity-template:retry",
})
sort.Strings(actual) sort.Strings(actual)
sort.Strings(expected) sort.Strings(expected)
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {

View File

@ -6,5 +6,6 @@ const (
CodeUnauthorized = 40100 CodeUnauthorized = 40100
CodeForbidden = 40300 CodeForbidden = 40300
CodeNotFound = 40400 CodeNotFound = 40400
CodeConflict = 40900
CodeServerError = 50000 CodeServerError = 50000
) )

View File

@ -26,6 +26,10 @@ func NotFound(c *gin.Context, message string) {
Fail(c, http.StatusNotFound, CodeNotFound, message) Fail(c, http.StatusNotFound, CodeNotFound, message)
} }
func Conflict(c *gin.Context, message string) {
Fail(c, http.StatusConflict, CodeConflict, message)
}
func ServerError(c *gin.Context, message string) { func ServerError(c *gin.Context, message string) {
Fail(c, http.StatusInternalServerError, CodeServerError, message) Fail(c, http.StatusInternalServerError, CodeServerError, message)
} }

View File

@ -4,6 +4,7 @@ import (
"hyapp-admin-server/internal/config" "hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/achievementconfig" "hyapp-admin-server/internal/modules/achievementconfig"
"hyapp-admin-server/internal/modules/activitytemplate"
"hyapp-admin-server/internal/modules/adminuser" "hyapp-admin-server/internal/modules/adminuser"
"hyapp-admin-server/internal/modules/agencyopening" "hyapp-admin-server/internal/modules/agencyopening"
"hyapp-admin-server/internal/modules/appconfig" "hyapp-admin-server/internal/modules/appconfig"
@ -70,6 +71,7 @@ import (
type Handlers struct { type Handlers struct {
AdminUser *adminuser.Handler AdminUser *adminuser.Handler
AgencyOpening *agencyopening.Handler AgencyOpening *agencyopening.Handler
ActivityTemplate *activitytemplate.Handler
AchievementConfig *achievementconfig.Handler AchievementConfig *achievementconfig.Handler
AppConfig *appconfig.Handler AppConfig *appconfig.Handler
AppRegistry *appregistry.Handler AppRegistry *appregistry.Handler
@ -142,6 +144,7 @@ func New(cfg config.Config, auth *service.AuthService, store *repository.Store,
authroutes.RegisterRoutes(api, protected, h.Auth) authroutes.RegisterRoutes(api, protected, h.Auth)
agencyopening.RegisterRoutes(appProtected, h.AgencyOpening) agencyopening.RegisterRoutes(appProtected, h.AgencyOpening)
activitytemplate.RegisterRoutes(appProtected, h.ActivityTemplate)
achievementconfig.RegisterRoutes(appProtected, h.AchievementConfig) achievementconfig.RegisterRoutes(appProtected, h.AchievementConfig)
coinledger.RegisterRoutes(appProtected, h.CoinLedger) coinledger.RegisterRoutes(appProtected, h.CoinLedger)
menu.RegisterRoutes(protected, h.Menu) menu.RegisterRoutes(protected, h.Menu)

View File

@ -2,10 +2,58 @@ SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 历史 admin_app_configs 没有租户列,所有 App 共同读取同一行。迁移把这些行保留为 app_code='' -- 历史 admin_app_configs 没有租户列,所有 App 共同读取同一行。迁移把这些行保留为 app_code=''
-- 的只读默认基线:未知或未来新增 App 仍能读取原配置,后续写操作只创建当前 App 的 scoped override。 -- 的只读默认基线:未知或未来新增 App 仍能读取原配置,后续写操作只创建当前 App 的 scoped override。
-- 同一个 key 可以同时存在一行 legacy 基线和多行 App override三元唯一键是隔离写入的最终兜底。 -- 早期 AutoMigrate 和 024 SQL 使用过两个不同的 legacy 唯一索引名;每一步均按元数据执行,
ALTER TABLE admin_app_configs -- 使全新数据库、任一历史形态以及失败后重跑都收敛到同一三元唯一键。
ADD COLUMN app_code VARCHAR(32) NOT NULL DEFAULT '' COMMENT '应用编码;空值仅表示迁移前的只读默认基线' AFTER id, SET @ddl := IF(
ADD COLUMN is_deleted BOOLEAN NOT NULL DEFAULT FALSE COMMENT '当前 App 删除墓碑;用于屏蔽默认基线' AFTER description, (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND COLUMN_NAME = 'app_code') = 0,
DROP INDEX uk_admin_app_configs_group_key, 'ALTER TABLE admin_app_configs ADD COLUMN app_code VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''应用编码;空值仅表示迁移前的只读默认基线'' AFTER id',
ADD UNIQUE KEY uk_admin_app_config_app_group_key (app_code, `group`, `key`), 'SELECT 1'
ADD KEY idx_admin_app_configs_app_group (app_code, `group`); );
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND COLUMN_NAME = 'is_deleted') = 0,
'ALTER TABLE admin_app_configs ADD COLUMN is_deleted BOOLEAN NOT NULL DEFAULT FALSE COMMENT ''当前 App 删除墓碑;用于屏蔽默认基线'' AFTER description',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND INDEX_NAME = 'uk_admin_app_configs_group_key') > 0,
'ALTER TABLE admin_app_configs DROP INDEX uk_admin_app_configs_group_key',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND INDEX_NAME = 'idx_admin_app_config_group_key') > 0,
'ALTER TABLE admin_app_configs DROP INDEX idx_admin_app_config_group_key',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND INDEX_NAME = 'uk_admin_app_config_app_group_key') = 0,
'ALTER TABLE admin_app_configs ADD UNIQUE KEY uk_admin_app_config_app_group_key (app_code, `group`, `key`)',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'admin_app_configs' AND INDEX_NAME = 'idx_admin_app_configs_app_group') = 0,
'ALTER TABLE admin_app_configs ADD KEY idx_admin_app_configs_app_group (app_code, `group`)',
'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -0,0 +1,62 @@
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 活动模版配置事实由 activity-service 持有;后台仅提供 RBAC、审计和 HTTP 到 gRPC 的协议适配。
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
('活动模版查看', 'activity-template:view', 'menu', '', @now_ms, @now_ms),
('活动模版创建', 'activity-template:create', 'button', '', @now_ms, @now_ms),
('活动模版更新', 'activity-template:update', 'button', '', @now_ms, @now_ms),
('活动模版发布', 'activity-template:publish', 'button', '', @now_ms, @now_ms),
('活动模版删除', 'activity-template:delete', 'button', '', @now_ms, @now_ms),
('活动模版数据', 'activity-template:data', 'button', '查看活动聚合数据、榜单与奖励发放记录', @now_ms, @now_ms),
('活动模版导出', 'activity-template:export', 'button', '导出受时间范围和维度上限约束的活动聚合数据', @now_ms, @now_ms),
('活动模版重试', 'activity-template:retry', 'button', '重试失败的活动榜单奖励发放任务', @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
kind = VALUES(kind),
description = VALUES(description),
updated_at_ms = @now_ms;
-- parent code 保持 activities 兼容既有路径和角色,但展示名按本期产品信息架构统一为“运营活动”。
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms) VALUES
(NULL, '运营活动', 'activities', '', 'campaign', '', 90, TRUE, @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = @now_ms;
INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms)
SELECT parent.id, '活动模版', 'activity-template', '/activities/templates', 'campaign', 'activity-template:view', 68, TRUE, @now_ms, @now_ms
FROM admin_menus parent
WHERE parent.code = 'activities'
ON DUPLICATE KEY UPDATE
parent_id = VALUES(parent_id),
title = VALUES(title),
path = VALUES(path),
icon = VALUES(icon),
permission_code = VALUES(permission_code),
sort = VALUES(sort),
visible = VALUES(visible),
updated_at_ms = @now_ms;
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT admin_role.id, admin_permission.id
FROM admin_roles admin_role
JOIN admin_permissions admin_permission
WHERE admin_role.code IN ('platform-admin', 'ops-admin', 'product-lead')
AND admin_permission.code IN (
'activity-template:view', 'activity-template:create', 'activity-template:update', 'activity-template:publish',
'activity-template:delete', 'activity-template:data', 'activity-template:export', 'activity-template:retry'
);
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT admin_role.id, admin_permission.id
FROM admin_roles admin_role
JOIN admin_permissions admin_permission
WHERE admin_role.code IN ('operations-specialist', 'product-specialist', 'auditor', 'readonly')
AND admin_permission.code IN ('activity-template:view', 'activity-template:data');

View File

@ -37,6 +37,13 @@ wheel_reward_worker:
batch_size: 50 batch_size: 50
lock_ttl: "30s" lock_ttl: "30s"
max_retry: 16 max_retry: 16
activity_template_stats_worker:
enabled: true
poll_interval: "1s"
batch_size: 100
lock_ttl: "30s"
activity_template_runtime:
settlement_lateness: "10m"
message_action_confirm_worker: message_action_confirm_worker:
enabled: true enabled: true
outbox_poll_interval: "1s" outbox_poll_interval: "1s"
@ -77,6 +84,7 @@ rocketmq:
enabled: true enabled: true
topic: "hyapp_room_outbox" topic: "hyapp_room_outbox"
consumer_group: "hyapp-activity-room-outbox" consumer_group: "hyapp-activity-room-outbox"
activity_template_consumer_group: "hyapp-activity-template-room-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
lucky_gift_outbox: lucky_gift_outbox:
enabled: true enabled: true
@ -103,6 +111,12 @@ rocketmq:
invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" invite_activity_consumer_group: "hyapp-activity-invite-user-outbox"
message_action_consumer_group: "hyapp-activity-message-action-user-outbox" message_action_consumer_group: "hyapp-activity-message-action-user-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
activity_template_outbox:
enabled: true
topic: "hyapp_activity_outbox"
producer_group: "hyapp-activity-template-outbox"
send_timeout: "5s"
retry: 2
message_action_outbox: message_action_outbox:
enabled: true enabled: true
topic: "hyapp_message_action_outbox" topic: "hyapp_message_action_outbox"

View File

@ -37,6 +37,13 @@ wheel_reward_worker:
batch_size: 50 batch_size: 50
lock_ttl: "30s" lock_ttl: "30s"
max_retry: 16 max_retry: 16
activity_template_stats_worker:
enabled: true
poll_interval: "1s"
batch_size: 100
lock_ttl: "30s"
activity_template_runtime:
settlement_lateness: "10m"
message_action_confirm_worker: message_action_confirm_worker:
enabled: true enabled: true
outbox_poll_interval: "1s" outbox_poll_interval: "1s"
@ -78,6 +85,7 @@ rocketmq:
enabled: true enabled: true
topic: "hyapp_room_outbox" topic: "hyapp_room_outbox"
consumer_group: "hyapp-activity-room-outbox" consumer_group: "hyapp-activity-room-outbox"
activity_template_consumer_group: "hyapp-activity-template-room-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
lucky_gift_outbox: lucky_gift_outbox:
enabled: true enabled: true
@ -104,6 +112,12 @@ rocketmq:
invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" invite_activity_consumer_group: "hyapp-activity-invite-user-outbox"
message_action_consumer_group: "hyapp-activity-message-action-user-outbox" message_action_consumer_group: "hyapp-activity-message-action-user-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
activity_template_outbox:
enabled: true
topic: "hyapp_activity_outbox"
producer_group: "hyapp-activity-template-outbox"
send_timeout: "5s"
retry: 2
message_action_outbox: message_action_outbox:
enabled: true enabled: true
topic: "hyapp_message_action_outbox" topic: "hyapp_message_action_outbox"

View File

@ -37,6 +37,13 @@ wheel_reward_worker:
batch_size: 50 batch_size: 50
lock_ttl: "30s" lock_ttl: "30s"
max_retry: 16 max_retry: 16
activity_template_stats_worker:
enabled: true
poll_interval: "1s"
batch_size: 100
lock_ttl: "30s"
activity_template_runtime:
settlement_lateness: "10m"
message_action_confirm_worker: message_action_confirm_worker:
enabled: true enabled: true
outbox_poll_interval: "1s" outbox_poll_interval: "1s"
@ -78,6 +85,7 @@ rocketmq:
enabled: true enabled: true
topic: "hyapp_room_outbox" topic: "hyapp_room_outbox"
consumer_group: "hyapp-activity-room-outbox" consumer_group: "hyapp-activity-room-outbox"
activity_template_consumer_group: "hyapp-activity-template-room-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
lucky_gift_outbox: lucky_gift_outbox:
enabled: true enabled: true
@ -104,6 +112,12 @@ rocketmq:
invite_activity_consumer_group: "hyapp-activity-invite-user-outbox" invite_activity_consumer_group: "hyapp-activity-invite-user-outbox"
message_action_consumer_group: "hyapp-activity-message-action-user-outbox" message_action_consumer_group: "hyapp-activity-message-action-user-outbox"
consumer_max_reconsume_times: 16 consumer_max_reconsume_times: 16
activity_template_outbox:
enabled: true
topic: "hyapp_activity_outbox"
producer_group: "hyapp-activity-template-outbox"
send_timeout: "5s"
retry: 2
message_action_outbox: message_action_outbox:
enabled: true enabled: true
topic: "hyapp_message_action_outbox" topic: "hyapp_message_action_outbox"

View File

@ -1479,3 +1479,366 @@ CREATE TABLE IF NOT EXISTS invite_activity_reward_claims (
KEY idx_invite_activity_reward_claim_user (app_code, cycle_key, user_id, created_at_ms), KEY idx_invite_activity_reward_claim_user (app_code, cycle_key, user_id, created_at_ms),
KEY idx_invite_activity_reward_claim_status (app_code, status, created_at_ms) KEY idx_invite_activity_reward_claim_status (app_code, status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动金币领取记录'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='邀请活动金币领取记录';
-- 活动模版配置和发布版本由 activity-service 统一持久化admin-server 只能通过 AdminActivityTemplateService 访问。
CREATE TABLE IF NOT EXISTS activity_templates (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
template_code VARCHAR(64) NOT NULL COMMENT 'App 内唯一业务编码',
name VARCHAR(128) NOT NULL COMMENT '后台内部名称',
activity_type VARCHAR(32) NOT NULL DEFAULT 'gift_challenge' COMMENT '活动类型',
status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/disabled/archived',
start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动开始UTC epoch ms包含',
end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动结束UTC epoch ms不包含',
all_regions BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否覆盖全部区域',
daily_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '日榜最多展示人数',
total_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '总榜最多展示人数',
display_config_json JSON NOT NULL COMMENT '客户端布局和样式配置',
revision BIGINT NOT NULL DEFAULT 1 COMMENT '后台编辑乐观锁版本',
published_version BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布快照版本',
created_by_admin_id BIGINT NOT NULL COMMENT '创建管理员',
updated_by_admin_id BIGINT NOT NULL COMMENT '最后更新管理员',
published_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布管理员',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
published_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布时间UTC epoch ms',
archived_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '软归档时间UTC epoch ms',
PRIMARY KEY (app_code, template_id),
UNIQUE KEY uk_activity_template_code (app_code, template_code),
KEY idx_activity_template_list (app_code, status, updated_at_ms, template_id),
KEY idx_activity_template_period (app_code, status, start_ms, end_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='可排期运营活动模版实例';
-- 发布互斥行按 App 串行化“检查重叠 + 写入 published”。仅锁候选活动行会出现两个草稿并发发布时互相不可见的写偏差。
CREATE TABLE IF NOT EXISTS activity_template_publish_locks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
updated_at_ms BIGINT NOT NULL COMMENT '最近获取发布锁时间UTC epoch ms',
PRIMARY KEY (app_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版发布事务互斥行';
CREATE TABLE IF NOT EXISTS activity_template_regions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
region_id BIGINT NOT NULL COMMENT '投放区域 ID',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, region_id),
KEY idx_activity_template_region_match (app_code, region_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版投放区域';
CREATE TABLE IF NOT EXISTS activity_template_locales (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
locale VARCHAR(24) NOT NULL COMMENT 'BCP-47 基础语言码',
title VARCHAR(160) NOT NULL DEFAULT '' COMMENT '客户端活动标题',
rules MEDIUMTEXT NOT NULL COMMENT '客户端活动规则正文',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版多语言文案';
CREATE TABLE IF NOT EXISTS activity_template_gifts (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
gift_id VARCHAR(96) NOT NULL COMMENT 'wallet 礼物 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '发布展示名称快照',
icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '发布展示图标快照',
coin_price BIGINT NOT NULL DEFAULT 0 COMMENT '发布时金币价格快照,仅供展示',
price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发布时价格版本快照',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, gift_id),
KEY idx_activity_template_gift_match (app_code, gift_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版指定礼物和展示快照';
CREATE TABLE IF NOT EXISTS activity_template_tasks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
task_key VARCHAR(64) NOT NULL COMMENT '模版内稳定任务键',
task_type VARCHAR(32) NOT NULL COMMENT 'daily_visit/gift_count/gift_coin_amount',
target_value BIGINT NOT NULL DEFAULT 0 COMMENT '任务达成阈值',
reward_resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, task_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版每日任务配置';
CREATE TABLE IF NOT EXISTS activity_template_rank_rewards (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
board_type VARCHAR(16) NOT NULL COMMENT 'daily/total',
rank_from INT NOT NULL COMMENT '奖励起始名次,闭区间',
rank_to INT NOT NULL COMMENT '奖励结束名次,闭区间',
resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, board_type, rank_from, rank_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版日榜和总榜名次奖励';
CREATE TABLE IF NOT EXISTS activity_template_assets (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
asset_key VARCHAR(64) NOT NULL COMMENT '素材位键',
locale VARCHAR(24) NOT NULL DEFAULT '*' COMMENT '* 表示共享素材,否则为语言码',
url VARCHAR(1024) NOT NULL COMMENT 'COS/CDN 素材地址',
media_type VARCHAR(16) NOT NULL COMMENT 'image/webp/svga/pag',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, asset_key, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版客户端素材';
CREATE TABLE IF NOT EXISTS activity_template_versions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
version_no BIGINT NOT NULL COMMENT '单模版递增发布版本',
snapshot_json JSON NOT NULL COMMENT '发布时完整不可变配置快照',
published_by_admin_id BIGINT NOT NULL COMMENT '发布管理员',
published_at_ms BIGINT NOT NULL COMMENT '发布时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_version_list (app_code, template_id, published_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变发布版本';
-- 活动模版真实运行时、任务、榜单结算及统计事实 outbox。
-- Activity-template runtime facts are version-bound. Configuration edits never rewrite these tables.
CREATE TABLE IF NOT EXISTS activity_template_runtime_versions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
start_ms BIGINT NOT NULL,
end_ms BIGINT NOT NULL,
all_regions BOOLEAN NOT NULL DEFAULT FALSE,
runtime_from_ms BIGINT NOT NULL,
runtime_to_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_runtime_current (app_code, template_code, start_ms, end_ms, runtime_from_ms, runtime_to_ms),
KEY idx_activity_template_runtime_due (app_code, end_ms, runtime_to_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变运行版本启停窗口';
CREATE TABLE IF NOT EXISTS activity_template_runtime_regions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
version_no BIGINT NOT NULL,
region_id BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, region_id),
KEY idx_activity_template_runtime_region (app_code, region_id, template_id, version_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行版本区域快照';
CREATE TABLE IF NOT EXISTS activity_template_event_consumption (
app_code VARCHAR(32) NOT NULL,
source_event_id VARCHAR(128) NOT NULL,
event_type VARCHAR(64) NOT NULL,
template_id VARCHAR(96) NOT NULL DEFAULT '',
template_code VARCHAR(64) NOT NULL DEFAULT '',
version_no BIGINT NOT NULL DEFAULT 0,
user_id BIGINT NOT NULL DEFAULT 0,
region_id BIGINT NOT NULL DEFAULT 0,
task_day VARCHAR(10) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
skip_reason VARCHAR(128) NOT NULL DEFAULT '',
occurred_at_ms BIGINT NOT NULL,
consumed_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, source_event_id),
KEY idx_activity_template_event_template (app_code, template_id, version_no, occurred_at_ms),
KEY idx_activity_template_event_status (app_code, status, consumed_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行事件幂等事实';
CREATE TABLE IF NOT EXISTS activity_template_user_day_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
day_start_ms BIGINT NOT NULL,
day_end_ms BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
KEY idx_activity_template_day_rank (app_code, template_id, version_no, task_day, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 UTC 日榜实时分数';
CREATE TABLE IF NOT EXISTS activity_template_user_total_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, user_id),
KEY idx_activity_template_total_rank (app_code, template_id, version_no, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版活动总榜实时分数';
CREATE TABLE IF NOT EXISTS activity_template_daily_visits (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
region_id BIGINT NOT NULL DEFAULT 0,
command_id VARCHAR(128) NOT NULL,
visited_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
UNIQUE KEY uk_activity_template_visit_command (app_code, command_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 daily_visit 自然幂等事实';
CREATE TABLE IF NOT EXISTS activity_template_task_progress (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
first_region_id BIGINT NOT NULL DEFAULT 0,
task_key VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL,
target_value BIGINT NOT NULL,
progress_value BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
sort_order INT NOT NULL DEFAULT 0,
status VARCHAR(24) NOT NULL,
completed_at_ms BIGINT NOT NULL DEFAULT 0,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
claimed_at_ms BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id, task_key),
KEY idx_activity_template_task_data (app_code, template_id, version_no, task_day, task_key, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版用户每日任务进度';
CREATE TABLE IF NOT EXISTS activity_template_task_claims (
app_code VARCHAR(32) NOT NULL,
claim_id VARCHAR(96) NOT NULL,
command_id VARCHAR(128) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
task_key VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(160) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, claim_id),
UNIQUE KEY uk_activity_template_claim_command (app_code, command_id),
UNIQUE KEY uk_activity_template_claim_task (app_code, template_id, version_no, task_day, user_id, task_key),
UNIQUE KEY uk_activity_template_claim_wallet (app_code, wallet_command_id),
KEY idx_activity_template_claim_status (app_code, status, updated_at_ms),
KEY idx_activity_template_claim_admin (app_code, template_id, version_no, task_day, task_key, user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版任务资源组领奖事实';
CREATE TABLE IF NOT EXISTS activity_template_rank_periods (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
period_start_ms BIGINT NOT NULL,
period_end_ms BIGINT NOT NULL,
settle_after_ms BIGINT NOT NULL COMMENT '结算水位周期结束加允许迟到窗口UTC epoch ms',
leaderboard_size INT NOT NULL,
status VARCHAR(24) NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
settled_at_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key),
KEY idx_activity_template_period_settle_due (app_code, status, settle_after_ms, lock_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单结算周期';
CREATE TABLE IF NOT EXISTS activity_template_rank_snapshots (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
gift_count BIGINT NOT NULL,
coin_amount BIGINT NOT NULL,
first_scored_at_ms BIGINT NOT NULL,
snapshot_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_rank_user (app_code, template_id, version_no, board_type, period_key, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版冻结榜单快照';
CREATE TABLE IF NOT EXISTS activity_template_reward_jobs (
app_code VARCHAR(32) NOT NULL,
reward_job_id VARCHAR(96) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(192) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
max_attempts INT NOT NULL DEFAULT 8 COMMENT '自动投递总尝试上限dead 手工复活时只扩一轮预算',
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次可领取时间UTC epoch ms',
locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前 job lease owner',
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'job lease 过期时间UTC epoch ms',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, reward_job_id),
UNIQUE KEY uk_activity_template_reward_rank (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_reward_wallet (app_code, wallet_command_id),
KEY idx_activity_template_reward_query (app_code, template_id, version_no, board_type, period_key, status),
KEY idx_activity_template_reward_retry (app_code, status, updated_at_ms),
KEY idx_activity_template_reward_claim (app_code, status, next_retry_at_ms, lock_until_ms, created_at_ms, reward_job_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单奖励发放任务';
CREATE TABLE IF NOT EXISTS activity_template_stats_outbox (
outbox_id VARCHAR(96) NOT NULL,
app_code VARCHAR(32) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload BLOB NOT NULL,
status VARCHAR(24) NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
next_retry_at_ms BIGINT NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
last_error VARCHAR(512) NOT NULL DEFAULT '',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (outbox_id),
KEY idx_activity_template_stats_outbox_claim (status, next_retry_at_ms, lock_until_ms, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版统计事实 RocketMQ transactional outbox';

View File

@ -0,0 +1,119 @@
CREATE TABLE IF NOT EXISTS activity_templates (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
template_code VARCHAR(64) NOT NULL COMMENT 'App 内唯一业务编码',
name VARCHAR(128) NOT NULL COMMENT '后台内部名称',
activity_type VARCHAR(32) NOT NULL DEFAULT 'gift_challenge' COMMENT '活动类型',
status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/disabled/archived',
start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动开始UTC epoch ms包含',
end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动结束UTC epoch ms不包含',
all_regions BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否覆盖全部区域',
daily_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '日榜最多展示人数',
total_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '总榜最多展示人数',
display_config_json JSON NOT NULL COMMENT '客户端布局和样式配置',
revision BIGINT NOT NULL DEFAULT 1 COMMENT '后台编辑乐观锁版本',
published_version BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布快照版本',
created_by_admin_id BIGINT NOT NULL COMMENT '创建管理员',
updated_by_admin_id BIGINT NOT NULL COMMENT '最后更新管理员',
published_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布管理员',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
published_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布时间UTC epoch ms',
archived_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '软归档时间UTC epoch ms',
PRIMARY KEY (app_code, template_id),
UNIQUE KEY uk_activity_template_code (app_code, template_code),
KEY idx_activity_template_list (app_code, status, updated_at_ms, template_id),
KEY idx_activity_template_period (app_code, status, start_ms, end_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='可排期运营活动模版实例';
-- 发布互斥行按 App 串行化“检查重叠 + 写入 published”。仅锁候选活动行会出现两个草稿并发发布时互相不可见的写偏差。
CREATE TABLE IF NOT EXISTS activity_template_publish_locks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
updated_at_ms BIGINT NOT NULL COMMENT '最近获取发布锁时间UTC epoch ms',
PRIMARY KEY (app_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版发布事务互斥行';
CREATE TABLE IF NOT EXISTS activity_template_regions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
region_id BIGINT NOT NULL COMMENT '投放区域 ID',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, region_id),
KEY idx_activity_template_region_match (app_code, region_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版投放区域';
CREATE TABLE IF NOT EXISTS activity_template_locales (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
locale VARCHAR(24) NOT NULL COMMENT 'BCP-47 基础语言码',
title VARCHAR(160) NOT NULL DEFAULT '' COMMENT '客户端活动标题',
rules MEDIUMTEXT NOT NULL COMMENT '客户端活动规则正文',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版多语言文案';
CREATE TABLE IF NOT EXISTS activity_template_gifts (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
gift_id VARCHAR(96) NOT NULL COMMENT 'wallet 礼物 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '发布展示名称快照',
icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '发布展示图标快照',
coin_price BIGINT NOT NULL DEFAULT 0 COMMENT '发布时金币价格快照,仅供展示',
price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发布时价格版本快照',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, gift_id),
KEY idx_activity_template_gift_match (app_code, gift_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版指定礼物和展示快照';
CREATE TABLE IF NOT EXISTS activity_template_tasks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
task_key VARCHAR(64) NOT NULL COMMENT '模版内稳定任务键',
task_type VARCHAR(32) NOT NULL COMMENT 'daily_visit/gift_count/gift_coin_amount',
target_value BIGINT NOT NULL DEFAULT 0 COMMENT '任务达成阈值',
reward_resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, task_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版每日任务配置';
CREATE TABLE IF NOT EXISTS activity_template_rank_rewards (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
board_type VARCHAR(16) NOT NULL COMMENT 'daily/total',
rank_from INT NOT NULL COMMENT '奖励起始名次,闭区间',
rank_to INT NOT NULL COMMENT '奖励结束名次,闭区间',
resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, board_type, rank_from, rank_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版日榜和总榜名次奖励';
CREATE TABLE IF NOT EXISTS activity_template_assets (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
asset_key VARCHAR(64) NOT NULL COMMENT '素材位键',
locale VARCHAR(24) NOT NULL DEFAULT '*' COMMENT '* 表示共享素材,否则为语言码',
url VARCHAR(1024) NOT NULL COMMENT 'COS/CDN 素材地址',
media_type VARCHAR(16) NOT NULL COMMENT 'image/webp/svga/pag',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, asset_key, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版客户端素材';
CREATE TABLE IF NOT EXISTS activity_template_versions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
version_no BIGINT NOT NULL COMMENT '单模版递增发布版本',
snapshot_json JSON NOT NULL COMMENT '发布时完整不可变配置快照',
published_by_admin_id BIGINT NOT NULL COMMENT '发布管理员',
published_at_ms BIGINT NOT NULL COMMENT '发布时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_version_list (app_code, template_id, published_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变发布版本';

View File

@ -0,0 +1,235 @@
-- Activity-template runtime facts are version-bound. Configuration edits never rewrite these tables.
CREATE TABLE IF NOT EXISTS activity_template_runtime_versions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
start_ms BIGINT NOT NULL,
end_ms BIGINT NOT NULL,
all_regions BOOLEAN NOT NULL DEFAULT FALSE,
runtime_from_ms BIGINT NOT NULL,
runtime_to_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_runtime_current (app_code, template_code, start_ms, end_ms, runtime_from_ms, runtime_to_ms),
KEY idx_activity_template_runtime_due (app_code, end_ms, runtime_to_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变运行版本启停窗口';
CREATE TABLE IF NOT EXISTS activity_template_runtime_regions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
version_no BIGINT NOT NULL,
region_id BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, region_id),
KEY idx_activity_template_runtime_region (app_code, region_id, template_id, version_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行版本区域快照';
CREATE TABLE IF NOT EXISTS activity_template_event_consumption (
app_code VARCHAR(32) NOT NULL,
source_event_id VARCHAR(128) NOT NULL,
event_type VARCHAR(64) NOT NULL,
template_id VARCHAR(96) NOT NULL DEFAULT '',
template_code VARCHAR(64) NOT NULL DEFAULT '',
version_no BIGINT NOT NULL DEFAULT 0,
user_id BIGINT NOT NULL DEFAULT 0,
region_id BIGINT NOT NULL DEFAULT 0,
task_day VARCHAR(10) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
skip_reason VARCHAR(128) NOT NULL DEFAULT '',
occurred_at_ms BIGINT NOT NULL,
consumed_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, source_event_id),
KEY idx_activity_template_event_template (app_code, template_id, version_no, occurred_at_ms),
KEY idx_activity_template_event_status (app_code, status, consumed_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行事件幂等事实';
CREATE TABLE IF NOT EXISTS activity_template_user_day_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
day_start_ms BIGINT NOT NULL,
day_end_ms BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
KEY idx_activity_template_day_rank (app_code, template_id, version_no, task_day, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 UTC 日榜实时分数';
CREATE TABLE IF NOT EXISTS activity_template_user_total_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, user_id),
KEY idx_activity_template_total_rank (app_code, template_id, version_no, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版活动总榜实时分数';
CREATE TABLE IF NOT EXISTS activity_template_daily_visits (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
region_id BIGINT NOT NULL DEFAULT 0,
command_id VARCHAR(128) NOT NULL,
visited_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
UNIQUE KEY uk_activity_template_visit_command (app_code, command_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 daily_visit 自然幂等事实';
CREATE TABLE IF NOT EXISTS activity_template_task_progress (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
first_region_id BIGINT NOT NULL DEFAULT 0,
task_key VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL,
target_value BIGINT NOT NULL,
progress_value BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
sort_order INT NOT NULL DEFAULT 0,
status VARCHAR(24) NOT NULL,
completed_at_ms BIGINT NOT NULL DEFAULT 0,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
claimed_at_ms BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id, task_key),
KEY idx_activity_template_task_data (app_code, template_id, version_no, task_day, task_key, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版用户每日任务进度';
CREATE TABLE IF NOT EXISTS activity_template_task_claims (
app_code VARCHAR(32) NOT NULL,
claim_id VARCHAR(96) NOT NULL,
command_id VARCHAR(128) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
task_key VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(160) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, claim_id),
UNIQUE KEY uk_activity_template_claim_command (app_code, command_id),
UNIQUE KEY uk_activity_template_claim_task (app_code, template_id, version_no, task_day, user_id, task_key),
UNIQUE KEY uk_activity_template_claim_wallet (app_code, wallet_command_id),
KEY idx_activity_template_claim_status (app_code, status, updated_at_ms),
KEY idx_activity_template_claim_admin (app_code, template_id, version_no, task_day, task_key, user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版任务资源组领奖事实';
CREATE TABLE IF NOT EXISTS activity_template_rank_periods (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
period_start_ms BIGINT NOT NULL,
period_end_ms BIGINT NOT NULL,
settle_after_ms BIGINT NOT NULL COMMENT '结算水位周期结束加允许迟到窗口UTC epoch ms',
leaderboard_size INT NOT NULL,
status VARCHAR(24) NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
settled_at_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key),
KEY idx_activity_template_period_settle_due (app_code, status, settle_after_ms, lock_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单结算周期';
CREATE TABLE IF NOT EXISTS activity_template_rank_snapshots (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
gift_count BIGINT NOT NULL,
coin_amount BIGINT NOT NULL,
first_scored_at_ms BIGINT NOT NULL,
snapshot_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_rank_user (app_code, template_id, version_no, board_type, period_key, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版冻结榜单快照';
CREATE TABLE IF NOT EXISTS activity_template_reward_jobs (
app_code VARCHAR(32) NOT NULL,
reward_job_id VARCHAR(96) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(192) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, reward_job_id),
UNIQUE KEY uk_activity_template_reward_rank (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_reward_wallet (app_code, wallet_command_id),
KEY idx_activity_template_reward_query (app_code, template_id, version_no, board_type, period_key, status),
KEY idx_activity_template_reward_retry (app_code, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单奖励发放任务';
CREATE TABLE IF NOT EXISTS activity_template_stats_outbox (
outbox_id VARCHAR(96) NOT NULL,
app_code VARCHAR(32) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload BLOB NOT NULL,
status VARCHAR(24) NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
next_retry_at_ms BIGINT NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
last_error VARCHAR(512) NOT NULL DEFAULT '',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (outbox_id),
KEY idx_activity_template_stats_outbox_claim (status, next_retry_at_ms, lock_until_ms, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版统计事实 RocketMQ transactional outbox';

View File

@ -0,0 +1,12 @@
-- 榜单周期冻结与钱包发奖解耦job 独立租约允许水平扩展,退避/终态避免钱包故障时形成重试风暴。
ALTER TABLE activity_template_reward_jobs
ADD COLUMN max_attempts INT NOT NULL DEFAULT 8
COMMENT '自动投递总尝试上限dead 手工复活时只扩一轮预算' AFTER attempt_count,
ADD COLUMN next_retry_at_ms BIGINT NOT NULL DEFAULT 0
COMMENT '下一次可领取时间UTC epoch ms' AFTER max_attempts,
ADD COLUMN locked_by VARCHAR(128) NOT NULL DEFAULT ''
COMMENT '当前 job lease owner' AFTER next_retry_at_ms,
ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0
COMMENT 'job lease 过期时间UTC epoch ms' AFTER locked_by,
ADD KEY idx_activity_template_reward_claim
(app_code, status, next_retry_at_ms, lock_until_ms, created_at_ms, reward_job_id);

View File

@ -0,0 +1,30 @@
package app
import (
"context"
"log/slog"
"time"
"hyapp/pkg/logx"
)
func (a *App) runActivityTemplateStatsOutboxWorker(ctx context.Context) {
options := a.activityTemplateStatsWorkerOptions
ticker := time.NewTicker(options.PollInterval)
defer ticker.Stop()
for {
claimed, _, err := a.activityTemplate.ProcessStatsOutboxBatch(ctx, a.activityTemplateProducer,
a.activityTemplateOutboxTopic, a.workerNodeID+":activity-template-stats", options.BatchSize, options.LockTTL)
if err != nil && ctx.Err() == nil {
logx.Error(ctx, "activity_template_stats_outbox_publish_failed", err, slog.String("node_id", a.workerNodeID))
}
if claimed >= options.BatchSize {
continue
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}

View File

@ -16,6 +16,7 @@ import (
"hyapp/pkg/servicekit/grpcserver" "hyapp/pkg/servicekit/grpcserver"
servicemq "hyapp/pkg/servicekit/mq" servicemq "hyapp/pkg/servicekit/mq"
"hyapp/services/activity-service/internal/config" "hyapp/services/activity-service/internal/config"
activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
broadcastservice "hyapp/services/activity-service/internal/service/broadcast" broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge" cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge"
firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge" firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge"
@ -39,16 +40,21 @@ type App struct {
inviteActivityReward *inviteactivityservice.Service inviteActivityReward *inviteactivityservice.Service
actionConfirm *messageservice.ActionConfirmService actionConfirm *messageservice.ActionConfirmService
wheel *wheelservice.Service wheel *wheelservice.Service
activityTemplate *activitytemplateservice.Service
broadcastWorkerEnabled bool broadcastWorkerEnabled bool
messageActionWorkerEnabled bool messageActionWorkerEnabled bool
wheelRewardWorkerEnabled bool wheelRewardWorkerEnabled bool
activityTemplateStatsWorkerEnabled bool
workerNodeID string workerNodeID string
messageActionWorkerOptions config.MessageActionConfirmWorkerConfig messageActionWorkerOptions config.MessageActionConfirmWorkerConfig
wheelRewardWorkerOptions config.WheelRewardWorkerConfig wheelRewardWorkerOptions config.WheelRewardWorkerConfig
activityTemplateStatsWorkerOptions config.ActivityTemplateStatsWorkerConfig
messageActionOutboxTopic string messageActionOutboxTopic string
activityTemplateOutboxTopic string
userLeaderboardRedisClose func() error userLeaderboardRedisClose func() error
mqConsumers []*rocketmqx.Consumer mqConsumers []*rocketmqx.Consumer
messageActionProducer *rocketmqx.Producer messageActionProducer *rocketmqx.Producer
activityTemplateProducer *rocketmqx.Producer
userConn *grpc.ClientConn userConn *grpc.ClientConn
walletConn *grpc.ClientConn walletConn *grpc.ClientConn
roomConn *grpc.ClientConn roomConn *grpc.ClientConn
@ -69,10 +75,11 @@ func New(cfg config.Config) (*App, error) {
var clients externalClients var clients externalClients
var mqConsumers []*rocketmqx.Consumer var mqConsumers []*rocketmqx.Consumer
var messageActionProducer *rocketmqx.Producer var messageActionProducer *rocketmqx.Producer
var activityTemplateProducer *rocketmqx.Producer
var userLeaderboardRedisClose func() error var userLeaderboardRedisClose func() error
cleanup := func() { cleanup := func() {
shutdownConsumers(mqConsumers) shutdownConsumers(mqConsumers)
servicemq.ShutdownProducers([]*rocketmqx.Producer{messageActionProducer}) servicemq.ShutdownProducers([]*rocketmqx.Producer{messageActionProducer, activityTemplateProducer})
if userLeaderboardRedisClose != nil { if userLeaderboardRedisClose != nil {
_ = userLeaderboardRedisClose() _ = userLeaderboardRedisClose()
} }
@ -120,6 +127,13 @@ func New(cfg config.Config) (*App, error) {
return nil, err return nil, err
} }
} }
if cfg.RocketMQ.ActivityTemplateOutbox.Enabled {
activityTemplateProducer, err = rocketmqx.NewProducer(activityTemplateProducerConfig(cfg.RocketMQ))
if err != nil {
cleanup()
return nil, err
}
}
health, healthHTTP, err := newHealthServers(cfg, server, repository) health, healthHTTP, err := newHealthServers(cfg, server, repository)
if err != nil { if err != nil {
cleanup() cleanup()
@ -138,16 +152,21 @@ func New(cfg config.Config) (*App, error) {
inviteActivityReward: services.inviteActivityReward, inviteActivityReward: services.inviteActivityReward,
actionConfirm: services.actionConfirm, actionConfirm: services.actionConfirm,
wheel: services.wheel, wheel: services.wheel,
activityTemplate: services.activityTemplate,
broadcastWorkerEnabled: cfg.Broadcast.Enabled, broadcastWorkerEnabled: cfg.Broadcast.Enabled,
messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled, messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled,
wheelRewardWorkerEnabled: cfg.WheelRewardWorker.Enabled, wheelRewardWorkerEnabled: cfg.WheelRewardWorker.Enabled,
activityTemplateStatsWorkerEnabled: cfg.ActivityTemplateStatsWorker.Enabled,
workerNodeID: cfg.NodeID, workerNodeID: cfg.NodeID,
messageActionWorkerOptions: cfg.MessageActionConfirmWorker, messageActionWorkerOptions: cfg.MessageActionConfirmWorker,
wheelRewardWorkerOptions: cfg.WheelRewardWorker, wheelRewardWorkerOptions: cfg.WheelRewardWorker,
activityTemplateStatsWorkerOptions: cfg.ActivityTemplateStatsWorker,
messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic, messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic,
activityTemplateOutboxTopic: cfg.RocketMQ.ActivityTemplateOutbox.Topic,
userLeaderboardRedisClose: userLeaderboardRedisClose, userLeaderboardRedisClose: userLeaderboardRedisClose,
mqConsumers: mqConsumers, mqConsumers: mqConsumers,
messageActionProducer: messageActionProducer, messageActionProducer: messageActionProducer,
activityTemplateProducer: activityTemplateProducer,
userConn: clients.userConn, userConn: clients.userConn,
walletConn: clients.walletConn, walletConn: clients.walletConn,
roomConn: clients.roomConn, roomConn: clients.roomConn,
@ -185,6 +204,11 @@ func (a *App) Run() error {
}) })
}) })
} }
if a.activityTemplateStatsWorkerEnabled && a.activityTemplate != nil && a.activityTemplateProducer != nil && a.workers != nil {
a.workers.Go(func(ctx context.Context) {
a.runActivityTemplateStatsOutboxWorker(ctx)
})
}
err := a.server.Serve(a.listener) err := a.server.Serve(a.listener)
if a.workers != nil { if a.workers != nil {
a.workers.StopAndWait() a.workers.StopAndWait()
@ -248,7 +272,7 @@ func (a *App) closeHealthHTTP() {
} }
func (a *App) startMQ() error { func (a *App) startMQ() error {
if err := servicemq.StartProducers([]*rocketmqx.Producer{a.messageActionProducer}); err != nil { if err := servicemq.StartProducers([]*rocketmqx.Producer{a.messageActionProducer, a.activityTemplateProducer}); err != nil {
return err return err
} }
return servicemq.StartConsumers(a.mqConsumers) return servicemq.StartConsumers(a.mqConsumers)
@ -256,5 +280,5 @@ func (a *App) startMQ() error {
func (a *App) shutdownMQ() { func (a *App) shutdownMQ() {
servicemq.ShutdownConsumers(a.mqConsumers) servicemq.ShutdownConsumers(a.mqConsumers)
servicemq.ShutdownProducers([]*rocketmqx.Producer{a.messageActionProducer}) servicemq.ShutdownProducers([]*rocketmqx.Producer{a.messageActionProducer, a.activityTemplateProducer})
} }

View File

@ -23,6 +23,7 @@ func openRepository(ctx context.Context, cfg config.Config) (*mysqlstorage.Repos
if err != nil { if err != nil {
return nil, err return nil, err
} }
repository.SetActivityTemplateSettlementLateness(cfg.ActivityTemplateRuntime.SettlementLateness)
if cfg.MySQLAutoMigrate { if cfg.MySQLAutoMigrate {
if err := repository.Migrate(ctx); err != nil { if err := repository.Migrate(ctx); err != nil {
_ = repository.Close() _ = repository.Close()

View File

@ -8,11 +8,13 @@ import (
func registerGRPCServers(server *grpc.Server, services *serviceBundle) { func registerGRPCServers(server *grpc.Server, services *serviceBundle) {
activityv1.RegisterActivityServiceServer(server, grpcserver.NewServer(services.activity)) activityv1.RegisterActivityServiceServer(server, grpcserver.NewServer(services.activity))
activityv1.RegisterAdminActivityTemplateServiceServer(server, grpcserver.NewAdminActivityTemplateServer(services.activityTemplate))
activityv1.RegisterActivityTemplateRuntimeServiceServer(server, grpcserver.NewActivityTemplateRuntimeServer(services.activityTemplate))
activityv1.RegisterMessageInboxServiceServer(server, grpcserver.NewMessageServer(services.message)) activityv1.RegisterMessageInboxServiceServer(server, grpcserver.NewMessageServer(services.message))
activityv1.RegisterMessageActionConfirmServiceServer(server, grpcserver.NewActionConfirmServer(services.actionConfirm)) activityv1.RegisterMessageActionConfirmServiceServer(server, grpcserver.NewActionConfirmServer(services.actionConfirm))
// cron 入口只做调度适配;具体状态机仍在各 activity service 内部。 // cron 入口只做调度适配;具体状态机仍在各 activity service 内部。
// weekly-star 和 room-turnover 都依赖 wallet 发奖,所以这里必须把同一批已装配的 service 注入 cron server。 // weekly-star 和 room-turnover 都依赖 wallet 发奖,所以这里必须把同一批已装配的 service 注入 cron server。
activityv1.RegisterActivityCronServiceServer(server, grpcserver.NewCronServer(services.message, services.growth, services.achievement, services.weeklyStar, services.roomTurnoverReward, services.agencyOpening, services.cpWeeklyRank)) activityv1.RegisterActivityCronServiceServer(server, grpcserver.NewCronServer(services.message, services.growth, services.achievement, services.weeklyStar, services.roomTurnoverReward, services.agencyOpening, services.cpWeeklyRank, services.activityTemplate))
activityv1.RegisterTaskServiceServer(server, grpcserver.NewTaskServer(services.task)) activityv1.RegisterTaskServiceServer(server, grpcserver.NewTaskServer(services.task))
activityv1.RegisterAdminTaskServiceServer(server, grpcserver.NewAdminTaskServer(services.task)) activityv1.RegisterAdminTaskServiceServer(server, grpcserver.NewAdminTaskServer(services.task))
activityv1.RegisterGrowthLevelServiceServer(server, grpcserver.NewGrowthLevelServer(services.growth)) activityv1.RegisterGrowthLevelServiceServer(server, grpcserver.NewGrowthLevelServer(services.growth))
@ -36,7 +38,7 @@ func registerGRPCServers(server *grpc.Server, services *serviceBundle) {
activityv1.RegisterAdminRoomTurnoverRewardServiceServer(server, grpcserver.NewAdminRoomTurnoverRewardServer(services.roomTurnoverReward)) activityv1.RegisterAdminRoomTurnoverRewardServiceServer(server, grpcserver.NewAdminRoomTurnoverRewardServer(services.roomTurnoverReward))
// BroadcastServer 同时承载 BroadcastService 和 RoomEventConsumerService。 // BroadcastServer 同时承载 BroadcastService 和 RoomEventConsumerService。
// gRPC 直连投递和 MQ 消费都必须经过相同的 room event fanout 顺序,否则本地验证会通过但线上 MQ 路径漏积分。 // gRPC 直连投递和 MQ 消费都必须经过相同的 room event fanout 顺序,否则本地验证会通过但线上 MQ 路径漏积分。
broadcastServer := grpcserver.NewBroadcastServer(services.broadcast, services.growth, services.weeklyStar, services.task, services.roomTurnoverReward, services.agencyOpening) broadcastServer := grpcserver.NewBroadcastServer(services.broadcast, services.growth, services.weeklyStar, services.task, services.roomTurnoverReward, services.agencyOpening, services.activityTemplate)
activityv1.RegisterBroadcastServiceServer(server, broadcastServer) activityv1.RegisterBroadcastServiceServer(server, broadcastServer)
activityv1.RegisterRoomEventConsumerServiceServer(server, broadcastServer) activityv1.RegisterRoomEventConsumerServiceServer(server, broadcastServer)
activityv1.RegisterFirstRechargeRewardServiceServer(server, grpcserver.NewFirstRechargeRewardServer(services.firstRechargeReward)) activityv1.RegisterFirstRechargeRewardServiceServer(server, grpcserver.NewFirstRechargeRewardServer(services.firstRechargeReward))

View File

@ -27,6 +27,12 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx.
return nil, err return nil, err
} }
mqConsumers = append(mqConsumers, consumer) mqConsumers = append(mqConsumers, consumer)
activityTemplateConsumer, err := newActivityTemplateRoomOutboxConsumer(cfg, services)
if err != nil {
shutdownConsumers(mqConsumers)
return nil, err
}
mqConsumers = append(mqConsumers, activityTemplateConsumer)
} }
if cfg.RocketMQ.LuckyGiftOutbox.Enabled { if cfg.RocketMQ.LuckyGiftOutbox.Enabled {
consumer, err := newLuckyGiftOutboxConsumer(cfg, services) consumer, err := newLuckyGiftOutboxConsumer(cfg, services)
@ -177,6 +183,34 @@ func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketm
return consumer, nil return consumer, nil
} }
func newActivityTemplateRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
consumer, err := rocketmqx.NewConsumer(activityTemplateRoomOutboxConsumerConfig(cfg.RocketMQ))
if err != nil {
return nil, err
}
tagExpression, err := roommq.LegacyCompatibleTagExpression(roommq.EventTypeRoomGiftSent)
if err != nil {
_ = consumer.Shutdown()
return nil, err
}
if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, tagExpression, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body)
if err != nil {
return err
}
if _, err := services.activityTemplate.HandleRoomEvent(appcode.WithContext(ctx, envelope.GetAppCode()), envelope); err != nil {
// Returning the error preserves RocketMQ retry/DLQ semantics on this dedicated consumer group without
// delaying unrelated broadcast/growth/task projections.
return fmt.Errorf("room outbox activity template projection: %w", err)
}
return nil
}); err != nil {
_ = consumer.Shutdown()
return nil, err
}
return consumer, nil
}
func activityRoomEventTypes() []string { func activityRoomEventTypes() []string {
// 六个投影共用同一 consumer group因此 selector 必须取其事件集合并集。 // 六个投影共用同一 consumer group因此 selector 必须取其事件集合并集。
// RoomGiftSent 驱动增长/任务/周星/流水,另外三个事件只驱动播报;其他房间 // RoomGiftSent 驱动增长/任务/周星/流水,另外三个事件只驱动播报;其他房间
@ -447,6 +481,14 @@ func roomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfi
return consumerConfig return consumerConfig
} }
func activityTemplateRoomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
consumerConfig := rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ActivityTemplateConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes)
// Gift scoring serializes per queue to reduce rank/task row lock inversions; horizontal instances still
// distribute RocketMQ queues inside this dedicated group.
consumerConfig.ConsumeGoroutines = 1
return consumerConfig
}
func luckyGiftOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig { func luckyGiftOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
// 顶部飘屏是实时副作用,新建或误改 group 时不能从 topic 最早位点重放历史大奖; // 顶部飘屏是实时副作用,新建或误改 group 时不能从 topic 最早位点重放历史大奖;
// 首次上线必须先部署 consumer再开启 lucky-gift producer确保 latest offset 不丢上线窗口事实。 // 首次上线必须先部署 consumer再开启 lucky-gift producer确保 latest offset 不丢上线窗口事实。
@ -503,6 +545,17 @@ func messageActionProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerCo
} }
} }
func activityTemplateProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig {
return rocketmqx.ProducerConfig{
EndpointConfig: rocketmqx.EndpointConfig{
NameServers: cfg.NameServers, NameServerDomain: cfg.NameServerDomain, AccessKey: cfg.AccessKey,
SecretKey: cfg.SecretKey, SecurityToken: cfg.SecurityToken, Namespace: cfg.Namespace, VIPChannel: cfg.VIPChannel,
},
GroupName: cfg.ActivityTemplateOutbox.ProducerGroup, SendTimeout: cfg.ActivityTemplateOutbox.SendTimeout,
Retry: cfg.ActivityTemplateOutbox.Retry,
}
}
func redPacketWalletOutboxTopic(cfg config.WalletOutboxMQConfig) string { func redPacketWalletOutboxTopic(cfg config.WalletOutboxMQConfig) string {
// 钱包实时 topic 显式配置后,红包 worker 只消费实时通道,避免继续被普通账务 topic 的高频消息拖慢。 // 钱包实时 topic 显式配置后,红包 worker 只消费实时通道,避免继续被普通账务 topic 的高频消息拖慢。
if cfg.RealtimeTopic != "" { if cfg.RealtimeTopic != "" {

View File

@ -21,6 +21,19 @@ func TestRoomOutboxConsumerConfigSerializesCompositeProjection(t *testing.T) {
} }
} }
func TestActivityTemplateRoomConsumerUsesIndependentGiftProjectionGroup(t *testing.T) {
cfg := config.Default().RocketMQ
cfg.RoomOutbox.ConsumerGroup = "activity-room-composite-test"
cfg.RoomOutbox.ActivityTemplateConsumerGroup = "activity-template-gift-test"
consumerConfig := activityTemplateRoomOutboxConsumerConfig(cfg)
if consumerConfig.GroupName != cfg.RoomOutbox.ActivityTemplateConsumerGroup || consumerConfig.GroupName == cfg.RoomOutbox.ConsumerGroup {
t.Fatalf("activity template consumer must own an independent offset: %+v", consumerConfig)
}
if consumerConfig.ConsumeGoroutines != 1 {
t.Fatalf("activity template gift consumer goroutines = %d, want 1", consumerConfig.ConsumeGoroutines)
}
}
func TestLuckyGiftOutboxConsumerUsesIndependentRealtimeGroup(t *testing.T) { func TestLuckyGiftOutboxConsumerUsesIndependentRealtimeGroup(t *testing.T) {
cfg := config.Default().RocketMQ cfg := config.Default().RocketMQ
cfg.LuckyGiftOutbox.ConsumerGroup = "activity-lucky-gift-test" cfg.LuckyGiftOutbox.ConsumerGroup = "activity-lucky-gift-test"

View File

@ -11,6 +11,7 @@ import (
"hyapp/services/activity-service/internal/config" "hyapp/services/activity-service/internal/config"
achievementservice "hyapp/services/activity-service/internal/service/achievement" achievementservice "hyapp/services/activity-service/internal/service/achievement"
activityservice "hyapp/services/activity-service/internal/service/activity" activityservice "hyapp/services/activity-service/internal/service/activity"
activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening" agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening"
broadcastservice "hyapp/services/activity-service/internal/service/broadcast" broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank" cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank"
@ -30,6 +31,7 @@ import (
type serviceBundle struct { type serviceBundle struct {
activity *activityservice.Service activity *activityservice.Service
activityTemplate *activitytemplateservice.Service
message *messageservice.Service message *messageservice.Service
actionConfirm *messageservice.ActionConfirmService actionConfirm *messageservice.ActionConfirmService
task *taskservice.Service task *taskservice.Service
@ -53,9 +55,12 @@ type serviceBundle struct {
func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, clients externalClients, userLeaderboardStore *userleaderboard.Store) (*serviceBundle, error) { func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository, clients externalClients, userLeaderboardStore *userleaderboard.Store) (*serviceBundle, error) {
walletClient := walletv1.NewWalletServiceClient(clients.walletConn) walletClient := walletv1.NewWalletServiceClient(clients.walletConn)
roomClient := roomv1.NewRoomQueryServiceClient(clients.roomConn) roomClient := roomv1.NewRoomQueryServiceClient(clients.roomConn)
regionSource := activityclient.NewGRPCRegionSource(clients.userConn)
activitySvc := activityservice.New(activityservice.Config{NodeID: cfg.NodeID}, repository) activitySvc := activityservice.New(activityservice.Config{NodeID: cfg.NodeID}, repository)
activityTemplateSvc := activitytemplateservice.New(repository, walletClient, regionSource)
messageSvc := messageservice.New(messageservice.Config{NodeID: cfg.NodeID}, repository, messageservice.WithTargetSource(activityclient.NewGRPCUserTargetSource(clients.userConn))) messageSvc := messageservice.New(messageservice.Config{NodeID: cfg.NodeID}, repository, messageservice.WithTargetSource(activityclient.NewGRPCUserTargetSource(clients.userConn)))
activityTemplateSvc.BindNotice(messageSvc)
actionConfirmSvc := messageservice.NewActionConfirm(messageservice.Config{NodeID: cfg.NodeID}, repository, activityclient.NewGRPCRoleInvitationClient(clients.userConn)) actionConfirmSvc := messageservice.NewActionConfirm(messageservice.Config{NodeID: cfg.NodeID}, repository, activityclient.NewGRPCRoleInvitationClient(clients.userConn))
taskSvc := taskservice.New(repository, walletClient) taskSvc := taskservice.New(repository, walletClient)
registrationRewardSvc := registrationrewardservice.New(repository, walletClient) registrationRewardSvc := registrationrewardservice.New(repository, walletClient)
@ -92,7 +97,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
EnsureGroupsOnStartup: cfg.Broadcast.EnsureGroupsOnStartup, EnsureGroupsOnStartup: cfg.Broadcast.EnsureGroupsOnStartup,
EnsureGroupsInterval: cfg.Broadcast.EnsureGroupsInterval, EnsureGroupsInterval: cfg.Broadcast.EnsureGroupsInterval,
GroupIDPrefix: cfg.TencentIM.GroupIDPrefix, GroupIDPrefix: cfg.TencentIM.GroupIDPrefix,
}, repository, broadcastPublisher, activityclient.NewGRPCRegionSource(clients.userConn)) }, repository, broadcastPublisher, regionSource)
userProfileSource := activityclient.NewGRPCUserProfileSource(clients.userConn) userProfileSource := activityclient.NewGRPCUserProfileSource(clients.userConn)
broadcastSvc.SetSenderProfileSource(userProfileSource) broadcastSvc.SetSenderProfileSource(userProfileSource)
wheelSvc := wheelservice.New(repository, wheelservice.WithWallet(walletClient)) wheelSvc := wheelservice.New(repository, wheelservice.WithWallet(walletClient))
@ -102,6 +107,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
return &serviceBundle{ return &serviceBundle{
activity: activitySvc, activity: activitySvc,
activityTemplate: activityTemplateSvc,
message: messageSvc, message: messageSvc,
actionConfirm: actionConfirmSvc, actionConfirm: actionConfirmSvc,
task: taskSvc, task: taskSvc,

View File

@ -42,6 +42,10 @@ type Config struct {
UserLeaderboardWorker UserLeaderboardWorkerConfig `yaml:"user_leaderboard_worker"` UserLeaderboardWorker UserLeaderboardWorkerConfig `yaml:"user_leaderboard_worker"`
// WheelRewardWorker 补偿抽奖事务已经确定、但同步钱包发奖未完成的 WheelRewardSettlement outbox。 // WheelRewardWorker 补偿抽奖事务已经确定、但同步钱包发奖未完成的 WheelRewardSettlement outbox。
WheelRewardWorker WheelRewardWorkerConfig `yaml:"wheel_reward_worker"` WheelRewardWorker WheelRewardWorkerConfig `yaml:"wheel_reward_worker"`
// ActivityTemplateStatsWorker relays committed runtime facts to statistics-service through RocketMQ.
ActivityTemplateStatsWorker ActivityTemplateStatsWorkerConfig `yaml:"activity_template_stats_worker"`
// ActivityTemplateRuntime 固化迟到事件水位等运行策略;发布时会写入物化周期,后续改配置不重写历史版本。
ActivityTemplateRuntime ActivityTemplateRuntimeConfig `yaml:"activity_template_runtime"`
// TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。 // TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。
TencentIM TencentIMConfig `yaml:"tencent_im"` TencentIM TencentIMConfig `yaml:"tencent_im"`
// Broadcast 控制 IM 播报群 reconciler、outbox worker 和贵重礼物阈值。 // Broadcast 控制 IM 播报群 reconciler、outbox worker 和贵重礼物阈值。
@ -133,6 +137,17 @@ type WheelRewardWorkerConfig struct {
MaxRetry int `yaml:"max_retry"` MaxRetry int `yaml:"max_retry"`
} }
type ActivityTemplateStatsWorkerConfig struct {
Enabled bool `yaml:"enabled"`
PollInterval time.Duration `yaml:"poll_interval"`
BatchSize int `yaml:"batch_size"`
LockTTL time.Duration `yaml:"lock_ttl"`
}
type ActivityTemplateRuntimeConfig struct {
SettlementLateness time.Duration `yaml:"settlement_lateness"`
}
// TencentIMConfig 描述 activity-service 调用腾讯云 IM REST API 的配置。 // TencentIMConfig 描述 activity-service 调用腾讯云 IM REST API 的配置。
type TencentIMConfig struct { type TencentIMConfig struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
@ -195,6 +210,7 @@ type RocketMQConfig struct {
WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"`
UserOutbox UserOutboxMQConfig `yaml:"user_outbox"` UserOutbox UserOutboxMQConfig `yaml:"user_outbox"`
MessageActionOutbox MessageActionOutboxMQConfig `yaml:"message_action_outbox"` MessageActionOutbox MessageActionOutboxMQConfig `yaml:"message_action_outbox"`
ActivityTemplateOutbox ActivityTemplateOutboxMQConfig `yaml:"activity_template_outbox"`
} }
// RoomOutboxMQConfig 控制 activity 对 room_outbox topic 的消费位点。 // RoomOutboxMQConfig 控制 activity 对 room_outbox topic 的消费位点。
@ -202,6 +218,7 @@ type RoomOutboxMQConfig struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
Topic string `yaml:"topic"` Topic string `yaml:"topic"`
ConsumerGroup string `yaml:"consumer_group"` ConsumerGroup string `yaml:"consumer_group"`
ActivityTemplateConsumerGroup string `yaml:"activity_template_consumer_group"`
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
} }
@ -249,6 +266,14 @@ type MessageActionOutboxMQConfig struct {
Retry int `yaml:"retry"` Retry int `yaml:"retry"`
} }
type ActivityTemplateOutboxMQConfig struct {
Enabled bool `yaml:"enabled"`
Topic string `yaml:"topic"`
ProducerGroup string `yaml:"producer_group"`
SendTimeout time.Duration `yaml:"send_timeout"`
Retry int `yaml:"retry"`
}
// Default 返回本地默认配置。 // Default 返回本地默认配置。
func Default() Config { func Default() Config {
return Config{ return Config{
@ -287,6 +312,10 @@ func Default() Config {
LockTTL: 30 * time.Second, LockTTL: 30 * time.Second,
MaxRetry: 16, MaxRetry: 16,
}, },
ActivityTemplateStatsWorker: ActivityTemplateStatsWorkerConfig{
Enabled: true, PollInterval: time.Second, BatchSize: 100, LockTTL: 30 * time.Second,
},
ActivityTemplateRuntime: ActivityTemplateRuntimeConfig{SettlementLateness: 10 * time.Minute},
MessageActionConfirmWorker: MessageActionConfirmWorkerConfig{ MessageActionConfirmWorker: MessageActionConfirmWorkerConfig{
Enabled: true, Enabled: true,
OutboxPollInterval: time.Second, OutboxPollInterval: time.Second,
@ -338,6 +367,7 @@ func defaultRocketMQConfig() RocketMQConfig {
Enabled: false, Enabled: false,
Topic: "hyapp_room_outbox", Topic: "hyapp_room_outbox",
ConsumerGroup: "hyapp-activity-room-outbox", ConsumerGroup: "hyapp-activity-room-outbox",
ActivityTemplateConsumerGroup: "hyapp-activity-template-room-outbox",
ConsumerMaxReconsumeTimes: 16, ConsumerMaxReconsumeTimes: 16,
}, },
LuckyGiftOutbox: LuckyGiftOutboxMQConfig{ LuckyGiftOutbox: LuckyGiftOutboxMQConfig{
@ -375,6 +405,10 @@ func defaultRocketMQConfig() RocketMQConfig {
SendTimeout: 5 * time.Second, SendTimeout: 5 * time.Second,
Retry: 2, Retry: 2,
}, },
ActivityTemplateOutbox: ActivityTemplateOutboxMQConfig{
Enabled: false, Topic: "hyapp_activity_outbox", ProducerGroup: "hyapp-activity-template-outbox",
SendTimeout: 5 * time.Second, Retry: 2,
},
} }
} }
@ -484,6 +518,21 @@ func Load(path string) (Config, error) {
if cfg.WheelRewardWorker.MaxRetry <= 0 { if cfg.WheelRewardWorker.MaxRetry <= 0 {
cfg.WheelRewardWorker.MaxRetry = 16 cfg.WheelRewardWorker.MaxRetry = 16
} }
if cfg.ActivityTemplateStatsWorker.PollInterval <= 0 {
cfg.ActivityTemplateStatsWorker.PollInterval = time.Second
}
if cfg.ActivityTemplateStatsWorker.BatchSize <= 0 {
cfg.ActivityTemplateStatsWorker.BatchSize = 100
}
if cfg.ActivityTemplateStatsWorker.LockTTL <= 0 {
cfg.ActivityTemplateStatsWorker.LockTTL = 30 * time.Second
}
if cfg.ActivityTemplateRuntime.SettlementLateness <= 0 {
cfg.ActivityTemplateRuntime.SettlementLateness = 10 * time.Minute
}
if cfg.ActivityTemplateRuntime.SettlementLateness > 30*time.Minute {
return Config{}, errors.New("activity_template_runtime.settlement_lateness must not exceed 30m")
}
rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ) rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
if err != nil { if err != nil {
return Config{}, err return Config{}, err
@ -506,6 +555,9 @@ func Load(path string) (Config, error) {
if cfg.MessageActionConfirmWorker.Enabled && (!cfg.RocketMQ.UserOutbox.Enabled || !cfg.RocketMQ.MessageActionOutbox.Enabled) { if cfg.MessageActionConfirmWorker.Enabled && (!cfg.RocketMQ.UserOutbox.Enabled || !cfg.RocketMQ.MessageActionOutbox.Enabled) {
return Config{}, errors.New("message action confirm worker requires rocketmq.user_outbox.enabled and rocketmq.message_action_outbox.enabled") return Config{}, errors.New("message action confirm worker requires rocketmq.user_outbox.enabled and rocketmq.message_action_outbox.enabled")
} }
if cfg.ActivityTemplateStatsWorker.Enabled && !cfg.RocketMQ.ActivityTemplateOutbox.Enabled {
return Config{}, errors.New("activity template stats worker requires rocketmq.activity_template_outbox.enabled")
}
return cfg, nil return cfg, nil
} }
@ -540,6 +592,9 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) {
if cfg.RoomOutbox.ConsumerGroup = strings.TrimSpace(cfg.RoomOutbox.ConsumerGroup); cfg.RoomOutbox.ConsumerGroup == "" { if cfg.RoomOutbox.ConsumerGroup = strings.TrimSpace(cfg.RoomOutbox.ConsumerGroup); cfg.RoomOutbox.ConsumerGroup == "" {
cfg.RoomOutbox.ConsumerGroup = defaults.RoomOutbox.ConsumerGroup cfg.RoomOutbox.ConsumerGroup = defaults.RoomOutbox.ConsumerGroup
} }
if cfg.RoomOutbox.ActivityTemplateConsumerGroup = strings.TrimSpace(cfg.RoomOutbox.ActivityTemplateConsumerGroup); cfg.RoomOutbox.ActivityTemplateConsumerGroup == "" {
cfg.RoomOutbox.ActivityTemplateConsumerGroup = defaults.RoomOutbox.ActivityTemplateConsumerGroup
}
if cfg.RoomOutbox.ConsumerMaxReconsumeTimes <= 0 { if cfg.RoomOutbox.ConsumerMaxReconsumeTimes <= 0 {
cfg.RoomOutbox.ConsumerMaxReconsumeTimes = defaults.RoomOutbox.ConsumerMaxReconsumeTimes cfg.RoomOutbox.ConsumerMaxReconsumeTimes = defaults.RoomOutbox.ConsumerMaxReconsumeTimes
} }
@ -610,7 +665,19 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) {
if cfg.MessageActionOutbox.Retry < 0 { if cfg.MessageActionOutbox.Retry < 0 {
cfg.MessageActionOutbox.Retry = defaults.MessageActionOutbox.Retry cfg.MessageActionOutbox.Retry = defaults.MessageActionOutbox.Retry
} }
if cfg.RoomOutbox.Enabled || cfg.LuckyGiftOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled { if cfg.ActivityTemplateOutbox.Topic = strings.TrimSpace(cfg.ActivityTemplateOutbox.Topic); cfg.ActivityTemplateOutbox.Topic == "" {
cfg.ActivityTemplateOutbox.Topic = defaults.ActivityTemplateOutbox.Topic
}
if cfg.ActivityTemplateOutbox.ProducerGroup = strings.TrimSpace(cfg.ActivityTemplateOutbox.ProducerGroup); cfg.ActivityTemplateOutbox.ProducerGroup == "" {
cfg.ActivityTemplateOutbox.ProducerGroup = defaults.ActivityTemplateOutbox.ProducerGroup
}
if cfg.ActivityTemplateOutbox.SendTimeout <= 0 {
cfg.ActivityTemplateOutbox.SendTimeout = defaults.ActivityTemplateOutbox.SendTimeout
}
if cfg.ActivityTemplateOutbox.Retry < 0 {
cfg.ActivityTemplateOutbox.Retry = defaults.ActivityTemplateOutbox.Retry
}
if cfg.RoomOutbox.Enabled || cfg.LuckyGiftOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled || cfg.ActivityTemplateOutbox.Enabled {
cfg.Enabled = true cfg.Enabled = true
} }
if cfg.Enabled { if cfg.Enabled {

View File

@ -0,0 +1,207 @@
package activitytemplate
const (
// ActivityTypeGiftChallenge 是当前原型对应的礼物挑战赛;类型字段保留,后续模板类型可复用同一生命周期框架。
ActivityTypeGiftChallenge = "gift_challenge"
StatusDraft = "draft"
StatusPublished = "published"
StatusDisabled = "disabled"
StatusArchived = "archived"
LifecycleDraft = "draft"
LifecycleScheduled = "scheduled"
LifecycleOngoing = "ongoing"
LifecycleEnded = "ended"
LifecycleDisabled = "disabled"
LifecycleArchived = "archived"
TaskTypeDailyVisit = "daily_visit"
TaskTypeGiftCount = "gift_count"
TaskTypeGiftCoinAmount = "gift_coin_amount"
BoardTypeDaily = "daily"
BoardTypeTotal = "total"
SharedAssetLocale = "*"
)
// Locale 是活动页的单语言文案。运行侧先匹配用户语言,再回退到英语,避免把后台内部名称暴露给客户端。
type Locale struct {
Locale string `json:"locale"`
Title string `json:"title"`
Rules string `json:"rules"`
}
// Gift 是参与任务和排行榜计分的礼物引用;礼物价格和上下架状态仍由礼物 owner 服务维护。
type Gift struct {
GiftID string `json:"gift_id"`
SortOrder int32 `json:"sort_order"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
CoinPrice int64 `json:"coin_price"`
PriceVersion string `json:"price_version"`
}
// RewardItem is the safe client-facing projection copied from a wallet-owned pinned resource-group
// snapshot. It intentionally excludes grant strategies, admin IDs and mutable catalog metadata.
type RewardItem struct {
ItemType string `json:"item_type"`
ResourceID int64 `json:"resource_id"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
WalletAssetType string `json:"wallet_asset_type"`
WalletAssetAmount int64 `json:"wallet_asset_amount"`
SortOrder int32 `json:"sort_order"`
}
type RewardSnapshot struct {
SourceGroupID int64 `json:"source_group_id"`
SnapshotID string `json:"snapshot_id"`
VersionNo int64 `json:"version_no"`
SnapshotHash string `json:"snapshot_hash"`
Name string `json:"name"`
Items []RewardItem `json:"items"`
}
// Task 是活动实例内的每日任务档位。奖励引用资源组,使复杂资产组合仍由 wallet-service 原子发放。
type Task struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TargetValue int64 `json:"target_value"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshot RewardSnapshot `json:"reward_snapshot"`
}
// RankReward 是一个不重叠的名次区间。区间使用闭区间,活动周期本身仍使用 [start_ms,end_ms)。
type RankReward struct {
BoardType string `json:"board_type"`
RankFrom int32 `json:"rank_from"`
RankTo int32 `json:"rank_to"`
ResourceGroupID int64 `json:"resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshot RewardSnapshot `json:"reward_snapshot"`
}
// Asset 是活动页素材清单Locale="*" 是共享素材,具体语言素材覆盖共享项。
type Asset struct {
AssetKey string `json:"asset_key"`
Locale string `json:"locale"`
URL string `json:"url"`
MediaType string `json:"media_type"`
SortOrder int32 `json:"sort_order"`
}
// Template 是运营后台称作“活动模版”的可排期活动实例。
// Revision 用于后台乐观锁PublishedVersion 指向最近一次发布形成的不可变完整快照,两者不能混用。
type Template struct {
AppCode string `json:"app_code"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
Name string `json:"name"`
ActivityType string `json:"activity_type"`
Status string `json:"status"`
LifecycleStatus string `json:"lifecycle_status"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
AllRegions bool `json:"all_regions"`
RegionIDs []int64 `json:"region_ids"`
Locales []Locale `json:"locales"`
Gifts []Gift `json:"gifts"`
Tasks []Task `json:"tasks"`
DailyLeaderboardSize int32 `json:"daily_leaderboard_size"`
TotalLeaderboardSize int32 `json:"total_leaderboard_size"`
RankRewards []RankReward `json:"rank_rewards"`
Assets []Asset `json:"assets"`
DisplayConfigJSON string `json:"display_config_json"`
Revision int64 `json:"revision"`
PublishedVersion int64 `json:"published_version"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
PublishedByAdminID int64 `json:"published_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
PublishedAtMS int64 `json:"published_at_ms"`
ArchivedAtMS int64 `json:"archived_at_ms"`
}
// Summary 是列表专用轻量投影,只附带区域范围,不加载规则、任务、榜单奖励和素材大字段。
type Summary struct {
AppCode string
TemplateID string
TemplateCode string
Name string
ActivityType string
Status string
LifecycleStatus string
StartMS int64
EndMS int64
AllRegions bool
RegionIDs []int64
Revision int64
PublishedVersion int64
CreatedByAdminID int64
UpdatedByAdminID int64
CreatedAtMS int64
UpdatedAtMS int64
}
// ListQuery 是后台列表筛选。Status 同时接受持久状态和 scheduled/ongoing/ended 生命周期状态。
type ListQuery struct {
Keyword string
Status string
RegionID *int64
AllRegions *bool
StartMS int64
EndMS int64
Page int32
PageSize int32
NowMS int64
}
// UpdateCommand 承载整份配置替换;子配置必须和主表在一个事务里提交,避免读到半份模板。
type UpdateCommand struct {
Template Template
ExpectedRevision int64
OperatorAdminID int64
}
// StatusCommand 是发布、停用和软归档的乐观锁命令。
type StatusCommand struct {
TemplateID string
Status string
ExpectedRevision int64
OperatorAdminID int64
RewardSnapshots map[int64]RewardSnapshot
}
// Version 是一次发布的不可变快照。Snapshot 让后台可以审阅历史版本,运行侧也能按版本稳定读取配置。
type Version struct {
TemplateID string
VersionNo int64
Snapshot Template
PublishedByAdminID int64
PublishedAtMS int64
RuntimeFromMS int64
RuntimeToMS int64
}
// CloneCommand 明确复制源版本和目标业务编码;源版本为 0 时复制当前编辑态配置。
type CloneCommand struct {
SourceTemplateID string
SourceVersion int64
TemplateCode string
Name string
OperatorAdminID int64
}
// ValidationIssue 是可绑定到表单字段的稳定校验结果Code 给前端做国际化Message 便于后台直接展示。
type ValidationIssue struct {
Field string
Code string
Message string
}

View File

@ -0,0 +1,225 @@
package activitytemplate
const (
TaskStatusInProgress = "in_progress"
TaskStatusCompleted = "completed"
TaskStatusClaimed = "claimed"
ClaimStatusPending = "pending"
ClaimStatusRunning = "running"
ClaimStatusGranted = "granted"
ClaimStatusFailed = "failed"
ClaimStatusDead = "dead"
PeriodStatusPending = "pending"
PeriodStatusSettling = "settling"
PeriodStatusRewarding = "rewarding"
PeriodStatusSettled = "settled"
PeriodStatusCancelled = "cancelled"
EventStatusConsumed = "consumed"
EventStatusSkipped = "skipped"
)
// Runtime 是一次发布形成的不可变运行版本及其真实启停窗口。
// Template 来自 activity_template_versions.snapshot_json不读取后台当前编辑态。
type Runtime struct {
Template Template
VersionNo int64
RuntimeFromMS int64
RuntimeToMS int64
}
// GiftEvent 是 room-service 已提交并完成钱包扣费的送礼事实。
type GiftEvent struct {
EventID string
UserID int64
GiftID string
GiftCount int64
CoinAmount int64
RegionID int64
OccurredAtMS int64
}
type EventResult struct {
EventID string
Status string
SkipReason string
TemplateID string
TemplateCode string
VersionNo int64
TaskDay string
}
// TaskProgress 固化任务目标和奖励资源组,后台后续发布新版本不会改变已产生的进度与领奖内容。
type TaskProgress struct {
AppCode string
TemplateID string
TemplateCode string
VersionNo int64
TaskDay string
UserID int64
TaskKey string
TaskType string
TargetValue int64
ProgressValue int64
RewardResourceGroupID int64
RewardSnapshot RewardSnapshot
SortOrder int32
Status string
CompletedAtMS int64
ClaimedAtMS int64
UpdatedAtMS int64
FirstRegionID int64
EarningRegionID int64
AttributionAtMS int64
}
type VisitCommand struct {
UserID int64
RegionID int64
TemplateCode string
CommandID string
VersionNo int64
}
type VisitResult struct {
Runtime Runtime
Tasks []TaskProgress
FirstVisit bool
}
type ClaimCommand struct {
UserID int64
RegionID int64
TemplateCode string
VersionNo int64
TaskDay string
TaskKey string
CommandID string
}
type TaskClaim struct {
AppCode string
ClaimID string
CommandID string
TemplateID string
TemplateCode string
VersionNo int64
TaskDay string
TaskKey string
UserID int64
RewardResourceGroupID int64
RewardSnapshot RewardSnapshot
WalletCommandID string
WalletGrantID string
Status string
FailureReason string
AttemptCount int32
CreatedAtMS int64
UpdatedAtMS int64
GrantedAtMS int64
EarningRegionID int64
AttributionAtMS int64
}
type TaskClaimQuery struct {
TemplateID string
VersionNo int64
TaskDay string
TaskKey string
UserID int64
Status string
Page int32
PageSize int32
}
type LeaderboardEntry struct {
RankNo int32
UserID int64
Score int64
GiftCount int64
CoinAmount int64
FirstScoredAtMS int64
Snapshot bool
}
type RankPeriod struct {
AppCode string
TemplateID string
TemplateCode string
VersionNo int64
BoardType string
PeriodKey string
StartMS int64
EndMS int64
SettleAfterMS int64
LeaderboardSize int32
Status string
SettledAtMS int64
}
type LeaderboardQuery struct {
TemplateID string
TemplateCode string
VersionNo int64
RegionID int64
UserID int64
BoardType string
PeriodKey string
Page int32
PageSize int32
NowMS int64
}
type LeaderboardResult struct {
Runtime Runtime
Period RankPeriod
Entries []LeaderboardEntry
MyEntry LeaderboardEntry
MyFound bool
Total int64
}
type RewardJob struct {
AppCode string
RewardJobID string
TemplateID string
TemplateCode string
VersionNo int64
BoardType string
PeriodKey string
RankNo int32
UserID int64
Score int64
ResourceGroupID int64
RewardSnapshot RewardSnapshot
WalletCommandID string
WalletGrantID string
Status string
FailureReason string
AttemptCount int32
MaxAttempts int32
NextRetryAtMS int64
CreatedAtMS int64
UpdatedAtMS int64
GrantedAtMS int64
}
type RewardJobQuery struct {
TemplateID string
VersionNo int64
BoardType string
PeriodKey string
Status string
Page int32
PageSize int32
}
// StatsOutboxRecord 是投递到 statistics-service 的 owner 事实;业务事务只写本地 outboxMQ 失败不回滚计分。
type StatsOutboxRecord struct {
OutboxID string
EventType string
Payload []byte
RetryCount int32
}

View File

@ -0,0 +1,671 @@
package activitytemplate
import (
"context"
"crypto/sha256"
"encoding/hex"
"log/slog"
"net/url"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/activitymq"
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/pkg/logx"
"hyapp/pkg/rocketmqx"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
messageservice "hyapp/services/activity-service/internal/service/message"
)
const (
activityTemplateTaskRewardReason = "activity template daily task reward"
activityTemplateRankRewardReason = "activity template leaderboard reward"
activityTemplateTaskGrantSource = "activity_template_task"
activityTemplateRankGrantSource = "activity_template_rank"
)
// RuntimeRepository is intentionally separate from the configuration Repository so legacy unit-test fakes
// remain small. Production MySQL implements both and New binds them through a checked type assertion.
type RuntimeRepository interface {
FindCurrentActivityTemplateRuntime(ctx context.Context, templateCode string, regionID int64, atMS int64) (domain.Runtime, bool, error)
FindActivityTemplateRuntimeForPeriod(ctx context.Context, templateCode string, versionNo int64, regionID int64, boardType string, periodKey string, atMS int64) (domain.Runtime, bool, error)
FindActivityTemplateRuntimeForUserTask(ctx context.Context, templateCode string, versionNo int64, userID int64, taskDay string, taskKey string) (domain.Runtime, bool, error)
FindActivityTemplateRuntimeForExactLeaderboard(ctx context.Context, templateCode string, versionNo int64, boardType string, periodKey string, atMS int64) (domain.Runtime, bool, error)
GetActivityTemplateRuntimeVersion(ctx context.Context, templateID string, versionNo int64) (domain.Runtime, error)
ConsumeActivityTemplateGiftEvent(ctx context.Context, event domain.GiftEvent, nowMS int64) (domain.EventResult, error)
VisitActivityTemplate(ctx context.Context, command domain.VisitCommand, nowMS int64) (domain.VisitResult, error)
ListActivityTemplateTaskProgress(ctx context.Context, runtime domain.Runtime, userID int64, taskDay string) ([]domain.TaskProgress, error)
PrepareActivityTemplateTaskClaim(ctx context.Context, runtime domain.Runtime, command domain.ClaimCommand, nowMS int64) (domain.TaskClaim, error)
MarkActivityTemplateTaskClaimGranted(ctx context.Context, claimID string, walletGrantID string, nowMS int64) (domain.TaskClaim, error)
MarkActivityTemplateTaskClaimFailed(ctx context.Context, claimID string, reason string, nowMS int64) error
ListActivityTemplateRuntimeLeaderboard(ctx context.Context, runtime domain.Runtime, query domain.LeaderboardQuery) (domain.LeaderboardResult, error)
ClaimDueActivityTemplateRankPeriods(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int32) ([]domain.RankPeriod, error)
PrepareActivityTemplateRankSettlement(ctx context.Context, period domain.RankPeriod, nowMS int64) ([]domain.RewardJob, error)
ClaimFinalizableActivityTemplateRankPeriods(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int32) ([]domain.RankPeriod, error)
ClaimActivityTemplateRewardJobs(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int32) ([]domain.RewardJob, error)
ExtendActivityTemplateRewardJobLease(ctx context.Context, rewardJobID string, workerID string, nowMS int64, lockTTL time.Duration) error
MarkActivityTemplateRewardJobGrantedWithLease(ctx context.Context, rewardJobID string, workerID string, walletGrantID string, nowMS int64) error
MarkActivityTemplateRewardJobFailedWithLease(ctx context.Context, rewardJobID string, workerID string, reason string, nowMS int64) error
FinishActivityTemplateRankPeriodIfComplete(ctx context.Context, period domain.RankPeriod, nowMS int64) (bool, error)
ListActivityTemplateRewardJobs(ctx context.Context, query domain.RewardJobQuery) ([]domain.RewardJob, int64, error)
ClaimActivityTemplateRewardJobForManualRetry(ctx context.Context, templateID string, rewardJobID string, workerID string, nowMS int64, lockTTL time.Duration) (domain.RewardJob, error)
ListActivityTemplateTaskClaims(ctx context.Context, query domain.TaskClaimQuery) ([]domain.TaskClaim, int64, error)
ClaimActivityTemplateTaskClaimForRetry(ctx context.Context, templateID string, claimID string, nowMS int64) (domain.TaskClaim, error)
ClaimActivityTemplateStatsOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.StatsOutboxRecord, error)
MarkActivityTemplateStatsOutboxSent(ctx context.Context, outboxID string, nowMS int64) error
MarkActivityTemplateStatsOutboxFailed(ctx context.Context, outboxID string, reason string, nowMS int64) error
}
type RuntimeWallet interface {
GrantPinnedResourceGroup(ctx context.Context, req *walletv1.GrantPinnedResourceGroupRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error)
}
type StatsPublisher interface {
SendSync(ctx context.Context, message rocketmqx.Message) error
}
type NoticeService interface {
CreateActivityNotice(ctx context.Context, cmd messageservice.NoticeCommand) (messageservice.NoticeResult, error)
}
// BindRuntime installs optional runtime dependencies without widening the stable configuration constructor.
// Production wiring treats a missing binding as Unavailable; configuration-only tests never call these methods.
func (s *Service) BindRuntime(repository RuntimeRepository, wallet RuntimeWallet) {
if s == nil {
return
}
s.runtime = repository
s.runtimeWallet = wallet
}
func (s *Service) BindNotice(notices NoticeService) {
if s != nil {
s.notices = notices
}
}
func (s *Service) GetCurrentRuntime(ctx context.Context, regionID int64, templateCode string) (domain.Runtime, bool, error) {
if err := s.requireRuntime(); err != nil {
return domain.Runtime{}, false, err
}
if regionID <= 0 {
return domain.Runtime{}, false, xerr.New(xerr.InvalidArgument, "region_id is required")
}
return s.runtime.FindCurrentActivityTemplateRuntime(ctx, strings.TrimSpace(templateCode), regionID, s.now().UTC().UnixMilli())
}
func (s *Service) Visit(ctx context.Context, command domain.VisitCommand) (domain.VisitResult, error) {
if err := s.requireRuntime(); err != nil {
return domain.VisitResult{}, err
}
command.TemplateCode = strings.TrimSpace(command.TemplateCode)
command.CommandID = strings.TrimSpace(command.CommandID)
if command.UserID <= 0 || command.RegionID <= 0 || command.VersionNo <= 0 || command.TemplateCode == "" || command.CommandID == "" || len(command.CommandID) > 128 {
return domain.VisitResult{}, xerr.New(xerr.InvalidArgument, "visit command is incomplete")
}
return s.runtime.VisitActivityTemplate(ctx, command, s.now().UTC().UnixMilli())
}
func (s *Service) ListRuntimeTasks(ctx context.Context, userID, regionID, versionNo int64, templateCode, taskDay string) (domain.Runtime, []domain.TaskProgress, int64, error) {
if err := s.requireRuntime(); err != nil {
return domain.Runtime{}, nil, 0, err
}
if userID <= 0 || versionNo < 0 || strings.TrimSpace(templateCode) == "" || (versionNo == 0 && regionID <= 0) {
return domain.Runtime{}, nil, 0, xerr.New(xerr.InvalidArgument, "task query is incomplete")
}
nowMS := s.now().UTC().UnixMilli()
var runtime domain.Runtime
var found bool
var err error
if versionNo > 0 {
if strings.TrimSpace(taskDay) == "" {
return domain.Runtime{}, nil, 0, xerr.New(xerr.InvalidArgument, "task_day is required for historical version")
}
runtime, found, err = s.runtime.FindActivityTemplateRuntimeForUserTask(ctx, strings.TrimSpace(templateCode), versionNo, userID, taskDay, "")
} else {
runtime, found, err = s.runtime.FindCurrentActivityTemplateRuntime(ctx, strings.TrimSpace(templateCode), regionID, nowMS)
}
if err != nil {
return domain.Runtime{}, nil, 0, err
}
if !found {
return domain.Runtime{}, nil, 0, xerr.New(xerr.NotFound, "active activity template not found")
}
if strings.TrimSpace(taskDay) == "" {
taskDay = utcDay(nowMS)
}
dayStart, err := parseUTCDay(taskDay)
if err != nil || dayStart >= runtime.Template.EndMS || dayStart+int64(24*time.Hour/time.Millisecond) <= runtime.RuntimeFromMS {
return domain.Runtime{}, nil, 0, xerr.New(xerr.InvalidArgument, "task_day is outside the activity runtime")
}
items, err := s.runtime.ListActivityTemplateTaskProgress(ctx, runtime, userID, taskDay)
if err != nil {
return domain.Runtime{}, nil, 0, err
}
nextRefresh := dayStart + int64(24*time.Hour/time.Millisecond)
if runtime.Template.EndMS < nextRefresh {
nextRefresh = runtime.Template.EndMS
}
return runtime, items, nextRefresh, nil
}
func (s *Service) ClaimRuntimeTaskReward(ctx context.Context, command domain.ClaimCommand) (domain.TaskClaim, error) {
if err := s.requireRuntime(); err != nil {
return domain.TaskClaim{}, err
}
if s.runtimeWallet == nil {
return domain.TaskClaim{}, xerr.New(xerr.Unavailable, "wallet client is not configured")
}
command.TemplateCode = strings.TrimSpace(command.TemplateCode)
command.TaskDay = strings.TrimSpace(command.TaskDay)
command.TaskKey = strings.TrimSpace(command.TaskKey)
command.CommandID = strings.TrimSpace(command.CommandID)
if command.UserID <= 0 || command.TemplateCode == "" || command.VersionNo <= 0 || command.TaskKey == "" || command.CommandID == "" || len(command.CommandID) > 128 {
return domain.TaskClaim{}, xerr.New(xerr.InvalidArgument, "task claim command is incomplete")
}
nowMS := s.now().UTC().UnixMilli()
if command.TaskDay == "" {
command.TaskDay = utcDay(nowMS)
}
if _, err := parseUTCDay(command.TaskDay); err != nil {
return domain.TaskClaim{}, err
}
// version_no 来自任务列表的不可变版本。即使同一 template_code 在同一个 UTC 日重发v1 的失败
// claim 也不会被 latest-v2 吞掉;物化 period 仍允许活动结束后的幂等 wallet 补偿。
runtime, found, err := s.runtime.FindActivityTemplateRuntimeForUserTask(
ctx, command.TemplateCode, command.VersionNo, command.UserID, command.TaskDay, command.TaskKey,
)
if err != nil {
return domain.TaskClaim{}, err
}
if !found {
return domain.TaskClaim{}, xerr.New(xerr.NotFound, "activity template task period not found")
}
claim, err := s.runtime.PrepareActivityTemplateTaskClaim(ctx, runtime, command, nowMS)
if err != nil || claim.Status == domain.ClaimStatusGranted {
return claim, err
}
if claim.RewardSnapshot.SnapshotID == "" {
err := xerr.New(xerr.Conflict, "activity task reward snapshot is missing")
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
resp, err := s.runtimeWallet.GrantPinnedResourceGroup(ctx, &walletv1.GrantPinnedResourceGroupRequest{
CommandId: claim.WalletCommandID, AppCode: appcode.FromContext(ctx), TargetUserId: claim.UserID,
SnapshotId: claim.RewardSnapshot.SnapshotID, Reason: activityTemplateTaskRewardReason,
OperatorUserId: claim.UserID, GrantSource: activityTemplateTaskGrantSource,
ExpectedSnapshotHash: claim.RewardSnapshot.SnapshotHash,
})
if err != nil {
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
walletGrantID := strings.TrimSpace(resp.GetGrant().GetGrantId())
if walletGrantID == "" {
err := xerr.New(xerr.Internal, "wallet returned an empty resource grant")
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
return s.runtime.MarkActivityTemplateTaskClaimGranted(ctx, claim.ClaimID, walletGrantID, s.now().UTC().UnixMilli())
}
func (s *Service) ListRuntimeLeaderboard(ctx context.Context, query domain.LeaderboardQuery) (domain.LeaderboardResult, error) {
if err := s.requireRuntime(); err != nil {
return domain.LeaderboardResult{}, err
}
query.TemplateCode = strings.TrimSpace(query.TemplateCode)
query.BoardType = strings.ToLower(strings.TrimSpace(query.BoardType))
query.PeriodKey = strings.TrimSpace(query.PeriodKey)
if query.UserID <= 0 || query.VersionNo < 0 || query.TemplateCode == "" || (query.VersionNo == 0 && query.RegionID <= 0) || (query.BoardType != domain.BoardTypeDaily && query.BoardType != domain.BoardTypeTotal) {
return domain.LeaderboardResult{}, xerr.New(xerr.InvalidArgument, "leaderboard query is invalid")
}
query.NowMS = s.now().UTC().UnixMilli()
var runtime domain.Runtime
var found bool
var err error
// 显式 period_key 是可分享、可从获奖通知回跳的稳定榜单身份。先按物化 period 解析其不可变发布版本,
// 因此活动结束或同一模版中途重发后仍能读取冻结榜;未显式指定时继续只暴露当前运行版本。
if query.VersionNo > 0 && query.PeriodKey == "" {
if query.BoardType == domain.BoardTypeTotal {
query.PeriodKey = "total"
} else {
query.PeriodKey = utcDay(query.NowMS)
}
}
if query.PeriodKey != "" && query.VersionNo > 0 {
runtime, found, err = s.runtime.FindActivityTemplateRuntimeForExactLeaderboard(
ctx, query.TemplateCode, query.VersionNo, query.BoardType, query.PeriodKey, query.NowMS,
)
} else if query.PeriodKey != "" {
runtime, found, err = s.runtime.FindActivityTemplateRuntimeForPeriod(
ctx, query.TemplateCode, query.VersionNo, query.RegionID, query.BoardType, query.PeriodKey, query.NowMS,
)
} else {
runtime, found, err = s.runtime.FindCurrentActivityTemplateRuntime(ctx, query.TemplateCode, query.RegionID, query.NowMS)
}
if err != nil {
return domain.LeaderboardResult{}, err
}
if !found {
return domain.LeaderboardResult{}, xerr.New(xerr.NotFound, "active activity template not found")
}
return s.runtime.ListActivityTemplateRuntimeLeaderboard(ctx, runtime, query)
}
func (s *Service) AdminListRuntimeLeaderboard(ctx context.Context, query domain.LeaderboardQuery) (domain.LeaderboardResult, error) {
if err := s.requireRuntime(); err != nil {
return domain.LeaderboardResult{}, err
}
query.TemplateID = strings.TrimSpace(query.TemplateID)
query.BoardType = strings.ToLower(strings.TrimSpace(query.BoardType))
if query.TemplateID == "" || query.VersionNo <= 0 || (query.BoardType != domain.BoardTypeDaily && query.BoardType != domain.BoardTypeTotal) {
return domain.LeaderboardResult{}, xerr.New(xerr.InvalidArgument, "admin leaderboard query is invalid")
}
query.NowMS = s.now().UTC().UnixMilli()
runtime, err := s.runtime.GetActivityTemplateRuntimeVersion(ctx, query.TemplateID, query.VersionNo)
if err != nil {
return domain.LeaderboardResult{}, err
}
return s.runtime.ListActivityTemplateRuntimeLeaderboard(ctx, runtime, query)
}
func (s *Service) ListRewardJobs(ctx context.Context, query domain.RewardJobQuery) ([]domain.RewardJob, int64, error) {
if err := s.requireRuntime(); err != nil {
return nil, 0, err
}
query.TemplateID = strings.TrimSpace(query.TemplateID)
query.BoardType = strings.ToLower(strings.TrimSpace(query.BoardType))
query.PeriodKey = strings.TrimSpace(query.PeriodKey)
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
if query.TemplateID == "" {
return nil, 0, xerr.New(xerr.InvalidArgument, "template_id is required")
}
if query.BoardType != "" && query.BoardType != domain.BoardTypeDaily && query.BoardType != domain.BoardTypeTotal {
return nil, 0, xerr.New(xerr.InvalidArgument, "board_type is invalid")
}
if query.Status != "" && query.Status != domain.ClaimStatusPending && query.Status != domain.ClaimStatusRunning && query.Status != domain.ClaimStatusGranted && query.Status != domain.ClaimStatusFailed && query.Status != domain.ClaimStatusDead {
return nil, 0, xerr.New(xerr.InvalidArgument, "reward status is invalid")
}
return s.runtime.ListActivityTemplateRewardJobs(ctx, query)
}
func (s *Service) ListTaskClaims(ctx context.Context, query domain.TaskClaimQuery) ([]domain.TaskClaim, int64, error) {
if err := s.requireRuntime(); err != nil {
return nil, 0, err
}
query.TemplateID = strings.TrimSpace(query.TemplateID)
query.TaskDay = strings.TrimSpace(query.TaskDay)
query.TaskKey = strings.TrimSpace(query.TaskKey)
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
if query.TemplateID == "" || query.VersionNo < 0 || query.UserID < 0 {
return nil, 0, xerr.New(xerr.InvalidArgument, "task claim query is invalid")
}
if query.TaskDay != "" {
if _, err := parseUTCDay(query.TaskDay); err != nil {
return nil, 0, err
}
}
if query.Status != "" && query.Status != domain.ClaimStatusPending && query.Status != domain.ClaimStatusRunning && query.Status != domain.ClaimStatusGranted && query.Status != domain.ClaimStatusFailed {
return nil, 0, xerr.New(xerr.InvalidArgument, "task claim status is invalid")
}
return s.runtime.ListActivityTemplateTaskClaims(ctx, query)
}
func (s *Service) RetryTaskClaim(ctx context.Context, templateID, claimID string, operatorAdminID int64) (domain.TaskClaim, error) {
if err := s.requireRuntime(); err != nil {
return domain.TaskClaim{}, err
}
if s.runtimeWallet == nil {
return domain.TaskClaim{}, xerr.New(xerr.Unavailable, "wallet client is not configured")
}
if strings.TrimSpace(templateID) == "" || strings.TrimSpace(claimID) == "" || operatorAdminID <= 0 {
return domain.TaskClaim{}, xerr.New(xerr.InvalidArgument, "task claim retry command is incomplete")
}
claim, err := s.runtime.ClaimActivityTemplateTaskClaimForRetry(ctx, strings.TrimSpace(templateID), strings.TrimSpace(claimID), s.now().UTC().UnixMilli())
if err != nil {
return domain.TaskClaim{}, err
}
if claim.RewardSnapshot.SnapshotID == "" || !validSHA256Hex(claim.RewardSnapshot.SnapshotHash) {
err := xerr.New(xerr.Conflict, "activity task reward snapshot is missing")
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
resp, err := s.runtimeWallet.GrantPinnedResourceGroup(ctx, &walletv1.GrantPinnedResourceGroupRequest{
CommandId: claim.WalletCommandID, AppCode: appcode.FromContext(ctx), TargetUserId: claim.UserID,
SnapshotId: claim.RewardSnapshot.SnapshotID, ExpectedSnapshotHash: claim.RewardSnapshot.SnapshotHash,
Reason: activityTemplateTaskRewardReason, OperatorUserId: claim.UserID, GrantSource: activityTemplateTaskGrantSource,
})
if err != nil {
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
walletGrantID := strings.TrimSpace(resp.GetGrant().GetGrantId())
if walletGrantID == "" {
err := xerr.New(xerr.Internal, "wallet returned an empty resource grant")
_ = s.runtime.MarkActivityTemplateTaskClaimFailed(ctx, claim.ClaimID, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return domain.TaskClaim{}, err
}
return s.runtime.MarkActivityTemplateTaskClaimGranted(ctx, claim.ClaimID, walletGrantID, s.now().UTC().UnixMilli())
}
// ProcessRuntimeSettlementBatch separates bounded period materialization from bounded job delivery. A large
// leaderboard can therefore never make one cron RPC perform unbounded wallet calls; every delivery is fenced by
// a per-invocation lease owner and remains replay-safe through the immutable wallet command.
func (s *Service) ProcessRuntimeSettlementBatch(ctx context.Context, runID, workerID string, batchSize int32, lockTTL time.Duration) (claimed, processed, success, failure int32, hasMore bool, err error) {
if err := s.requireRuntime(); err != nil {
return 0, 0, 0, 0, false, err
}
if s.runtimeWallet == nil {
return 0, 0, 0, 0, false, xerr.New(xerr.Unavailable, "wallet client is not configured")
}
if batchSize <= 0 {
batchSize = 20
}
nowMS := s.now().UTC().UnixMilli()
workerID = strings.TrimSpace(workerID)
if workerID == "" {
workerID = "activity-template-settlement"
}
runID = strings.TrimSpace(runID)
if runID == "" {
runID = idgen.New("run")
}
leaseOwner := activityTemplateRewardLeaseOwner(workerID, runID)
periods, err := s.runtime.ClaimDueActivityTemplateRankPeriods(ctx, leaseOwner, nowMS, lockTTL, batchSize)
if err != nil {
return 0, 0, 0, 0, false, err
}
for _, period := range periods {
_, prepareErr := s.runtime.PrepareActivityTemplateRankSettlement(ctx, period, s.now().UTC().UnixMilli())
if prepareErr != nil {
logx.Error(ctx, "activity_template_prepare_settlement_failed", prepareErr, slog.String("template_id", period.TemplateID), slog.String("period_key", period.PeriodKey), slog.String("run_id", runID))
failure++
continue
}
// Empty/no-reward boards settle here; boards with jobs are released from the period lock and
// finish after the independent job batch reaches all-granted.
if _, finishErr := s.runtime.FinishActivityTemplateRankPeriodIfComplete(ctx, period, s.now().UTC().UnixMilli()); finishErr != nil {
failure++
}
}
jobs, err := s.runtime.ClaimActivityTemplateRewardJobs(ctx, leaseOwner, s.now().UTC().UnixMilli(), lockTTL, batchSize)
if err != nil {
return 0, 0, 0, failure, len(periods) == int(batchSize), err
}
claimed = int32(len(jobs))
processed = int32(len(jobs))
var succeeded atomic.Int32
var failed atomic.Int32
workerCount := min(len(jobs), 8)
if workerCount > 0 {
queue := make(chan domain.RewardJob)
var workers sync.WaitGroup
workers.Add(workerCount)
for range workerCount {
go func() {
defer workers.Done()
for job := range queue {
if _, grantErr := s.grantRuntimeRewardJob(ctx, job, leaseOwner, lockTTL); grantErr != nil {
failed.Add(1)
continue
}
succeeded.Add(1)
}
}()
}
for _, job := range jobs {
queue <- job
}
close(queue)
workers.Wait()
}
success = succeeded.Load()
failure += failed.Load()
// A single job batch can span periods. Finalize each identity once after every wallet worker has stopped,
// so a period is never marked settled while an in-process job still owns a lease.
periodsToFinish := make(map[string]domain.RankPeriod)
for _, job := range jobs {
key := job.TemplateID + "\x00" + strconv.FormatInt(job.VersionNo, 10) + "\x00" + job.BoardType + "\x00" + job.PeriodKey
periodsToFinish[key] = domain.RankPeriod{
AppCode: job.AppCode, TemplateID: job.TemplateID, TemplateCode: job.TemplateCode, VersionNo: job.VersionNo,
BoardType: job.BoardType, PeriodKey: job.PeriodKey,
}
}
for _, period := range periodsToFinish {
if _, finishErr := s.runtime.FinishActivityTemplateRankPeriodIfComplete(ctx, period, s.now().UTC().UnixMilli()); finishErr != nil {
failure++
}
}
// Recover the narrow crash window after the last granted-job commit but before its period transition. The
// repository only returns `rewarding` periods with zero non-granted jobs, so failed/dead/backoff queues cannot
// consume this bound or keep cron in a has_more hot loop.
finalizable, finalizeClaimErr := s.runtime.ClaimFinalizableActivityTemplateRankPeriods(
ctx, leaseOwner, s.now().UTC().UnixMilli(), lockTTL, batchSize,
)
if finalizeClaimErr != nil {
return claimed, processed, success, failure, len(periods) == int(batchSize) || len(jobs) == int(batchSize), finalizeClaimErr
}
for _, period := range finalizable {
if _, finishErr := s.runtime.FinishActivityTemplateRankPeriodIfComplete(ctx, period, s.now().UTC().UnixMilli()); finishErr != nil {
failure++
}
}
hasMore = len(periods) == int(batchSize) || len(jobs) == int(batchSize) || len(finalizable) == int(batchSize)
return claimed, processed, success, failure, hasMore, nil
}
func (s *Service) RetryRewardJob(ctx context.Context, templateID, rewardJobID string, operatorAdminID int64) (domain.RewardJob, error) {
if err := s.requireRuntime(); err != nil {
return domain.RewardJob{}, err
}
if s.runtimeWallet == nil {
return domain.RewardJob{}, xerr.New(xerr.Unavailable, "wallet client is not configured")
}
if strings.TrimSpace(templateID) == "" || strings.TrimSpace(rewardJobID) == "" || operatorAdminID <= 0 {
return domain.RewardJob{}, xerr.New(xerr.InvalidArgument, "reward retry command is incomplete")
}
nowMS := s.now().UTC().UnixMilli()
leaseOwner := activityTemplateRewardLeaseOwner("admin-retry-"+strconv.FormatInt(operatorAdminID, 10), idgen.New("request")+":"+rewardJobID)
const manualRetryLeaseTTL = time.Minute
job, err := s.runtime.ClaimActivityTemplateRewardJobForManualRetry(
ctx, strings.TrimSpace(templateID), strings.TrimSpace(rewardJobID), leaseOwner, nowMS, manualRetryLeaseTTL,
)
if err != nil || job.Status == domain.ClaimStatusGranted {
return job, err
}
walletGrantID, err := s.grantRuntimeRewardJob(ctx, job, leaseOwner, manualRetryLeaseTTL)
if err != nil {
return domain.RewardJob{}, err
}
job.Status = domain.ClaimStatusGranted
job.WalletGrantID = walletGrantID
job.FailureReason = ""
job.GrantedAtMS = s.now().UTC().UnixMilli()
job.UpdatedAtMS = job.GrantedAtMS
return job, nil
}
func (s *Service) grantRuntimeRewardJob(ctx context.Context, job domain.RewardJob, leaseOwner string, lockTTL time.Duration) (string, error) {
markFailed := func(cause error) error {
if markErr := s.runtime.MarkActivityTemplateRewardJobFailedWithLease(ctx, job.RewardJobID, leaseOwner, xerr.MessageOf(cause), s.now().UTC().UnixMilli()); markErr != nil {
logx.Error(ctx, "activity_template_reward_job_failure_fence_lost", markErr,
slog.String("reward_job_id", job.RewardJobID), slog.String("lease_owner", leaseOwner))
}
return cause
}
if job.RewardSnapshot.SnapshotID == "" || !validSHA256Hex(job.RewardSnapshot.SnapshotHash) {
err := xerr.New(xerr.Conflict, "activity rank reward snapshot is missing")
return "", markFailed(err)
}
if err := s.runtime.ExtendActivityTemplateRewardJobLease(ctx, job.RewardJobID, leaseOwner, s.now().UTC().UnixMilli(), lockTTL); err != nil {
return "", err
}
resp, err := s.runtimeWallet.GrantPinnedResourceGroup(ctx, &walletv1.GrantPinnedResourceGroupRequest{
CommandId: job.WalletCommandID, AppCode: appcode.FromContext(ctx), TargetUserId: job.UserID,
SnapshotId: job.RewardSnapshot.SnapshotID, Reason: activityTemplateRankRewardReason,
OperatorUserId: job.UserID, GrantSource: activityTemplateRankGrantSource,
ExpectedSnapshotHash: job.RewardSnapshot.SnapshotHash,
})
if err != nil {
return "", markFailed(err)
}
walletGrantID := strings.TrimSpace(resp.GetGrant().GetGrantId())
if walletGrantID == "" {
err := xerr.New(xerr.Internal, "wallet returned an empty resource grant")
return "", markFailed(err)
}
// The wallet call can outlive the original lease window. Re-fence before emitting the user notice; if
// another worker took over, it will replay the same wallet command and stable notice event instead.
if err := s.runtime.ExtendActivityTemplateRewardJobLease(ctx, job.RewardJobID, leaseOwner, s.now().UTC().UnixMilli(), lockTTL); err != nil {
return "", err
}
if s.notices != nil {
action := url.Values{}
action.Set("template_code", job.TemplateCode)
action.Set("version_no", strconv.FormatInt(job.VersionNo, 10))
action.Set("board_type", job.BoardType)
action.Set("period_key", job.PeriodKey)
_, noticeErr := s.notices.CreateActivityNotice(ctx, messageservice.NoticeCommand{
TargetUserID: job.UserID, Producer: "activity-template-runtime",
ProducerEventID: job.RewardJobID + ":granted", ProducerEventType: "ActivityTemplateRankRewardGranted",
AggregateType: "activity_template_reward_job", AggregateID: job.RewardJobID,
TemplateID: job.TemplateID, TemplateVersion: strconv.FormatInt(job.VersionNo, 10),
// Inbox storage currently requires title/summary text. Persist stable content keys rather than
// English prose; App clients localize the keys with the immutable metadata parameters below.
Title: "activity_template.rank_reward.title", Summary: "activity_template.rank_reward.summary",
ActionType: "activity_detail",
ActionParam: action.Encode(), SentAtMS: s.now().UTC().UnixMilli(),
Metadata: map[string]any{
"content_key": "activity_template.rank_reward_granted", "template_code": job.TemplateCode,
"version_no": job.VersionNo, "board_type": job.BoardType, "period_key": job.PeriodKey,
"rank_no": job.RankNo, "reward_name": job.RewardSnapshot.Name,
"resource_group_id": job.ResourceGroupID,
},
})
if noticeErr != nil {
// Wallet command is stable. Marking failed makes the next cron/admin retry replay the same wallet
// grant (idempotent) and retry the inbox write instead of silently losing the winner notification.
return "", markFailed(noticeErr)
}
}
if err := s.runtime.MarkActivityTemplateRewardJobGrantedWithLease(ctx, job.RewardJobID, leaseOwner, walletGrantID, s.now().UTC().UnixMilli()); err != nil {
// 钱包与站内信均已幂等成功但本地状态落库失败时,立即释放结算周期并标为 failed下一轮会用同一
// wallet_command_id/producer_event_id 补偿,不会重复发奖或重复通知。
_ = s.runtime.MarkActivityTemplateRewardJobFailedWithLease(ctx, job.RewardJobID, leaseOwner, xerr.MessageOf(err), s.now().UTC().UnixMilli())
return "", err
}
return walletGrantID, nil
}
func activityTemplateRewardLeaseOwner(workerID, invocationID string) string {
workerID = strings.TrimSpace(workerID)
invocationID = strings.TrimSpace(invocationID)
owner := workerID + ":" + invocationID
if len(owner) <= 128 {
return owner
}
// Preserve an operator-readable prefix while bounding the persisted fencing token.
digest := sha256.Sum256([]byte(owner))
return "activity-template-reward-lease:" + hex.EncodeToString(digest[:])
}
// HandleRoomEvent accepts only wallet-committed real gift facts. coin_spent is the scoring source of truth;
// configured gift price is a display snapshot and is never multiplied in this path.
func (s *Service) HandleRoomEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) (int32, error) {
if s == nil || s.runtime == nil || envelope == nil || envelope.GetEventType() != "RoomGiftSent" {
return 0, nil
}
var gift roomeventsv1.RoomGiftSent
if err := proto.Unmarshal(envelope.GetBody(), &gift); err != nil {
return 0, err
}
if gift.GetIsRobotGift() || gift.GetSyntheticLuckyGift() || gift.GetRealRoomRobotGift() {
return 0, nil
}
if strings.TrimSpace(envelope.GetEventId()) == "" || gift.GetSenderUserId() <= 0 || strings.TrimSpace(gift.GetGiftId()) == "" || gift.GetGiftCount() <= 0 || gift.GetCoinSpent() <= 0 || gift.GetRegionId() <= 0 || envelope.GetOccurredAtMs() <= 0 {
return 0, nil
}
result, err := s.runtime.ConsumeActivityTemplateGiftEvent(appcode.WithContext(ctx, envelope.GetAppCode()), domain.GiftEvent{
EventID: strings.TrimSpace(envelope.GetEventId()), UserID: gift.GetSenderUserId(), GiftID: strings.TrimSpace(gift.GetGiftId()),
// region_id is the sender's profile region captured by room-service at wallet commit. visible_region_id
// describes the room audience and must never choose a user-targeted activity runtime.
GiftCount: int64(gift.GetGiftCount()), CoinAmount: gift.GetCoinSpent(), RegionID: gift.GetRegionId(),
OccurredAtMS: envelope.GetOccurredAtMs(),
}, s.now().UTC().UnixMilli())
if err != nil {
return 0, err
}
// Normal misses (no active runtime / gift not configured) are expected routing outcomes. Closed rank
// watermarks are operationally different: a committed gift arrived too late for at least one projection,
// so surface both partial acceptance and full rejection with delay metadata for alerting.
if strings.Contains(result.SkipReason, "settlement_window_closed") || strings.Contains(result.SkipReason, "rank_period_closed") {
logx.Warn(ctx, "activity_template_gift_rank_projection_late",
slog.String("event_id", result.EventID), slog.String("template_id", result.TemplateID),
slog.Int64("version_no", result.VersionNo), slog.String("task_day", result.TaskDay),
slog.String("projection_outcome", result.SkipReason), slog.Int64("processing_delay_ms", max(int64(0), s.now().UTC().UnixMilli()-envelope.GetOccurredAtMs())))
}
if result.Status == domain.EventStatusConsumed {
return 1, nil
}
return 0, nil
}
// ProcessStatsOutboxBatch publishes owner facts to the statistics topic. Sent is marked only after broker ack;
// a crash after ack but before MarkSent is harmless because statistics_event_consumption deduplicates event_id.
func (s *Service) ProcessStatsOutboxBatch(ctx context.Context, publisher StatsPublisher, topic, workerID string, batchSize int, lockTTL time.Duration) (int, int, error) {
if err := s.requireRuntime(); err != nil {
return 0, 0, err
}
if publisher == nil || strings.TrimSpace(topic) == "" {
return 0, 0, xerr.New(xerr.Unavailable, "activity template stats publisher is not configured")
}
items, err := s.runtime.ClaimActivityTemplateStatsOutbox(ctx, workerID, s.now().UTC().UnixMilli(), lockTTL, batchSize)
if err != nil {
return 0, 0, err
}
success := 0
for _, item := range items {
tag, tagErr := activitymq.EventTypeTag(item.EventType)
if tagErr == nil {
tagErr = publisher.SendSync(ctx, rocketmqx.Message{Topic: topic, Tag: tag, Keys: []string{item.OutboxID}, Body: item.Payload})
}
if tagErr != nil {
_ = s.runtime.MarkActivityTemplateStatsOutboxFailed(ctx, item.OutboxID, tagErr.Error(), s.now().UTC().UnixMilli())
continue
}
if err := s.runtime.MarkActivityTemplateStatsOutboxSent(ctx, item.OutboxID, s.now().UTC().UnixMilli()); err != nil {
return len(items), success, err
}
success++
}
return len(items), success, nil
}
func (s *Service) requireRuntime() error {
if s == nil || s.runtime == nil {
return xerr.New(xerr.Unavailable, "activity template runtime repository is not configured")
}
return nil
}
func utcDay(ms int64) string { return time.UnixMilli(ms).UTC().Format("2006-01-02") }
func parseUTCDay(value string) (int64, error) {
t, err := time.Parse("2006-01-02", strings.TrimSpace(value))
if err != nil || t.Format("2006-01-02") != strings.TrimSpace(value) {
return 0, xerr.New(xerr.InvalidArgument, "task_day must be UTC YYYY-MM-DD")
}
return t.UnixMilli(), nil
}

View File

@ -0,0 +1,451 @@
package activitytemplate
import (
"context"
"errors"
"strings"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
roomeventsv1 "hyapp.local/api/proto/events/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
messageservice "hyapp/services/activity-service/internal/service/message"
)
type leaderboardRuntimeRepository struct {
RuntimeRepository
currentRuntime domain.Runtime
currentFound bool
periodRuntime domain.Runtime
periodFound bool
currentCalls int
periodCalls int
periodVersionNo int64
leaderboardCall int
giftEvents []domain.GiftEvent
}
func (f *leaderboardRuntimeRepository) FindCurrentActivityTemplateRuntime(context.Context, string, int64, int64) (domain.Runtime, bool, error) {
f.currentCalls++
return f.currentRuntime, f.currentFound, nil
}
func (f *leaderboardRuntimeRepository) ConsumeActivityTemplateGiftEvent(_ context.Context, event domain.GiftEvent, _ int64) (domain.EventResult, error) {
f.giftEvents = append(f.giftEvents, event)
return domain.EventResult{EventID: event.EventID, Status: domain.EventStatusConsumed}, nil
}
func (f *leaderboardRuntimeRepository) FindActivityTemplateRuntimeForPeriod(_ context.Context, _ string, versionNo int64, _ int64, _ string, _ string, _ int64) (domain.Runtime, bool, error) {
f.periodCalls++
f.periodVersionNo = versionNo
return f.periodRuntime, f.periodFound, nil
}
func (f *leaderboardRuntimeRepository) FindActivityTemplateRuntimeForExactLeaderboard(_ context.Context, _ string, versionNo int64, _ string, _ string, _ int64) (domain.Runtime, bool, error) {
f.periodCalls++
f.periodVersionNo = versionNo
return f.periodRuntime, f.periodFound, nil
}
func (f *leaderboardRuntimeRepository) ListActivityTemplateRuntimeLeaderboard(_ context.Context, runtime domain.Runtime, query domain.LeaderboardQuery) (domain.LeaderboardResult, error) {
f.leaderboardCall++
return domain.LeaderboardResult{
Runtime: runtime,
Period: domain.RankPeriod{
TemplateID: runtime.Template.TemplateID, TemplateCode: runtime.Template.TemplateCode,
VersionNo: runtime.VersionNo, BoardType: query.BoardType, PeriodKey: query.PeriodKey,
},
Entries: []domain.LeaderboardEntry{},
}, nil
}
func TestExactLeaderboardAllowsNonParticipantCrossRegionViewerAfterActivityEnded(t *testing.T) {
now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC)
ended := domain.Runtime{
Template: domain.Template{TemplateID: "acttpl_1", TemplateCode: "summer_gifts", EndMS: now.Add(-time.Hour).UnixMilli()},
VersionNo: 4, RuntimeFromMS: now.Add(-7 * 24 * time.Hour).UnixMilli(), RuntimeToMS: now.Add(-time.Hour).UnixMilli(),
}
repository := &leaderboardRuntimeRepository{periodRuntime: ended, periodFound: true}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, nil)
svc.now = func() time.Time { return now }
got, err := svc.ListRuntimeLeaderboard(context.Background(), domain.LeaderboardQuery{
// RegionID is deliberately zero: the gateway does not resolve the viewer's mutable current region
// for exact historical links, and repository resolution does not require participation.
TemplateCode: "summer_gifts", VersionNo: 4, UserID: 70001, RegionID: 0, BoardType: domain.BoardTypeTotal,
PeriodKey: "total", Page: 1, PageSize: 20,
})
if err != nil {
t.Fatalf("ListRuntimeLeaderboard() error = %v", err)
}
if got.Runtime.VersionNo != 4 || repository.periodVersionNo != 4 || repository.periodCalls != 1 || repository.currentCalls != 0 || repository.leaderboardCall != 1 {
t.Fatalf("historical leaderboard resolution = %+v; repository=%+v", got.Runtime, repository)
}
}
func TestListRuntimeLeaderboardWithoutPeriodRemainsCurrentOnly(t *testing.T) {
repository := &leaderboardRuntimeRepository{}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, nil)
_, err := svc.ListRuntimeLeaderboard(context.Background(), domain.LeaderboardQuery{
TemplateCode: "summer_gifts", UserID: 70001, RegionID: 8, BoardType: domain.BoardTypeTotal,
})
if !xerr.IsCode(err, xerr.NotFound) {
t.Fatalf("ListRuntimeLeaderboard() error = %v, want not found", err)
}
if repository.currentCalls != 1 || repository.periodCalls != 0 {
t.Fatalf("unexpected lookup calls: current=%d period=%d", repository.currentCalls, repository.periodCalls)
}
}
func TestHandleRoomEventExcludesEveryRobotGiftClassAndUsesCoinSpent(t *testing.T) {
repository := &leaderboardRuntimeRepository{}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, nil)
now := time.Date(2026, 7, 14, 9, 0, 0, 0, time.UTC)
svc.now = func() time.Time { return now }
tests := []struct {
name string
edit func(*roomeventsv1.RoomGiftSent)
want int32
}{
{name: "is robot gift", edit: func(item *roomeventsv1.RoomGiftSent) { item.IsRobotGift = true }},
{name: "synthetic lucky gift", edit: func(item *roomeventsv1.RoomGiftSent) { item.SyntheticLuckyGift = true }},
{name: "real room robot gift", edit: func(item *roomeventsv1.RoomGiftSent) { item.RealRoomRobotGift = true }},
{name: "real user gift", edit: func(*roomeventsv1.RoomGiftSent) {}, want: 1},
}
for index, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
before := len(repository.giftEvents)
gift := &roomeventsv1.RoomGiftSent{
SenderUserId: 90001, GiftId: "rose", GiftCount: 3, GiftValue: 999_999,
CoinSpent: 750, RegionId: 12, VisibleRegionId: 99,
}
tt.edit(gift)
body, err := proto.Marshal(gift)
if err != nil {
t.Fatalf("marshal gift: %v", err)
}
consumed, err := svc.HandleRoomEvent(context.Background(), &roomeventsv1.EventEnvelope{
EventId: "evt-" + tt.name, EventType: "RoomGiftSent", AppCode: "hyapp",
OccurredAtMs: now.Add(time.Duration(index) * time.Millisecond).UnixMilli(), Body: body,
})
if err != nil || consumed != tt.want {
t.Fatalf("HandleRoomEvent() = (%d, %v), want (%d, nil)", consumed, err, tt.want)
}
if tt.want == 0 && len(repository.giftEvents) != before {
t.Fatalf("robot gift reached score repository: %+v", repository.giftEvents[before:])
}
})
}
if len(repository.giftEvents) != 1 || repository.giftEvents[0].CoinAmount != 750 || repository.giftEvents[0].GiftCount != 3 || repository.giftEvents[0].RegionID != 12 {
t.Fatalf("real gift score fact = %+v; coin_spent must be source of truth", repository.giftEvents)
}
}
type retryRewardRepository struct {
RuntimeRepository
job domain.RewardJob
taskRuntime domain.Runtime
taskClaim domain.TaskClaim
claimCalls int
taskClaimCalls int
taskVersionNo int64
markedGranted []string
markedFailed []string
taskGranted []string
taskFailed []string
walletGrantIDs []string
leaseOwners []string
}
func (f *retryRewardRepository) FindCurrentActivityTemplateRuntime(context.Context, string, int64, int64) (domain.Runtime, bool, error) {
return domain.Runtime{}, false, nil
}
func (f *retryRewardRepository) FindActivityTemplateRuntimeForUserTask(_ context.Context, _ string, versionNo int64, _ int64, _ string, _ string) (domain.Runtime, bool, error) {
f.taskVersionNo = versionNo
return f.taskRuntime, f.taskRuntime.VersionNo > 0, nil
}
func (f *retryRewardRepository) PrepareActivityTemplateTaskClaim(context.Context, domain.Runtime, domain.ClaimCommand, int64) (domain.TaskClaim, error) {
f.taskClaimCalls++
claim := f.taskClaim
claim.Status = domain.ClaimStatusRunning
claim.AttemptCount = int32(f.taskClaimCalls)
return claim, nil
}
func (f *retryRewardRepository) MarkActivityTemplateTaskClaimGranted(_ context.Context, claimID, walletGrantID string, nowMS int64) (domain.TaskClaim, error) {
f.taskGranted = append(f.taskGranted, claimID)
claim := f.taskClaim
claim.Status = domain.ClaimStatusGranted
claim.WalletGrantID = walletGrantID
claim.GrantedAtMS = nowMS
return claim, nil
}
func (f *retryRewardRepository) MarkActivityTemplateTaskClaimFailed(_ context.Context, claimID, _ string, _ int64) error {
f.taskFailed = append(f.taskFailed, claimID)
return nil
}
func (f *retryRewardRepository) ClaimActivityTemplateRewardJobForManualRetry(_ context.Context, _, _ string, workerID string, _ int64, _ time.Duration) (domain.RewardJob, error) {
f.claimCalls++
f.leaseOwners = append(f.leaseOwners, workerID)
job := f.job
job.Status = domain.ClaimStatusRunning
job.AttemptCount = int32(f.claimCalls)
return job, nil
}
func (f *retryRewardRepository) ExtendActivityTemplateRewardJobLease(_ context.Context, _, workerID string, _ int64, _ time.Duration) error {
f.leaseOwners = append(f.leaseOwners, workerID)
return nil
}
func (f *retryRewardRepository) MarkActivityTemplateRewardJobGrantedWithLease(_ context.Context, jobID, workerID, walletGrantID string, _ int64) error {
f.markedGranted = append(f.markedGranted, jobID)
f.leaseOwners = append(f.leaseOwners, workerID)
f.walletGrantIDs = append(f.walletGrantIDs, walletGrantID)
return nil
}
func (f *retryRewardRepository) MarkActivityTemplateRewardJobFailedWithLease(_ context.Context, jobID, workerID, _ string, _ int64) error {
f.markedFailed = append(f.markedFailed, jobID)
f.leaseOwners = append(f.leaseOwners, workerID)
return nil
}
type sequenceRuntimeWallet struct {
requests []*walletv1.GrantPinnedResourceGroupRequest
errors []error
}
func (f *sequenceRuntimeWallet) GrantPinnedResourceGroup(_ context.Context, req *walletv1.GrantPinnedResourceGroupRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) {
f.requests = append(f.requests, req)
index := len(f.requests) - 1
if index < len(f.errors) && f.errors[index] != nil {
return nil, f.errors[index]
}
return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "wallet-grant-1"}}, nil
}
type captureRuntimeNotice struct {
commands []messageservice.NoticeCommand
}
type settlementLeaseRepository struct {
RuntimeRepository
periods []domain.RankPeriod
finalizable []domain.RankPeriod
jobs []domain.RewardJob
periodOwner string
jobOwner string
leaseOwners []string
prepared []string
finished []string
granted []string
failed []string
}
func (f *settlementLeaseRepository) ClaimDueActivityTemplateRankPeriods(_ context.Context, workerID string, _ int64, _ time.Duration, _ int32) ([]domain.RankPeriod, error) {
f.periodOwner = workerID
return append([]domain.RankPeriod(nil), f.periods...), nil
}
func (f *settlementLeaseRepository) PrepareActivityTemplateRankSettlement(_ context.Context, period domain.RankPeriod, _ int64) ([]domain.RewardJob, error) {
f.prepared = append(f.prepared, period.BoardType+":"+period.PeriodKey)
return []domain.RewardJob{}, nil
}
func (f *settlementLeaseRepository) ClaimFinalizableActivityTemplateRankPeriods(_ context.Context, _ string, _ int64, _ time.Duration, _ int32) ([]domain.RankPeriod, error) {
return append([]domain.RankPeriod(nil), f.finalizable...), nil
}
func (f *settlementLeaseRepository) FinishActivityTemplateRankPeriodIfComplete(_ context.Context, period domain.RankPeriod, _ int64) (bool, error) {
f.finished = append(f.finished, period.BoardType+":"+period.PeriodKey)
return true, nil
}
func (f *settlementLeaseRepository) ClaimActivityTemplateRewardJobs(_ context.Context, workerID string, _ int64, _ time.Duration, _ int32) ([]domain.RewardJob, error) {
f.jobOwner = workerID
return append([]domain.RewardJob(nil), f.jobs...), nil
}
func (f *settlementLeaseRepository) ExtendActivityTemplateRewardJobLease(_ context.Context, _ string, workerID string, _ int64, _ time.Duration) error {
f.leaseOwners = append(f.leaseOwners, workerID)
return nil
}
func (f *settlementLeaseRepository) MarkActivityTemplateRewardJobGrantedWithLease(_ context.Context, jobID, workerID, _ string, _ int64) error {
f.leaseOwners = append(f.leaseOwners, workerID)
f.granted = append(f.granted, jobID)
return nil
}
func (f *settlementLeaseRepository) MarkActivityTemplateRewardJobFailedWithLease(_ context.Context, jobID, workerID, _ string, _ int64) error {
f.leaseOwners = append(f.leaseOwners, workerID)
f.failed = append(f.failed, jobID)
return nil
}
func TestSettlementSeparatesPeriodMaterializationFromBoundedLeasedJobDelivery(t *testing.T) {
periodDaily := domain.RankPeriod{AppCode: "hyapp", TemplateID: "tpl-1", TemplateCode: "summer", VersionNo: 3, BoardType: domain.BoardTypeDaily, PeriodKey: "2026-07-14"}
periodTotal := domain.RankPeriod{AppCode: "hyapp", TemplateID: "tpl-1", TemplateCode: "summer", VersionNo: 3, BoardType: domain.BoardTypeTotal, PeriodKey: "total"}
job := domain.RewardJob{
AppCode: "hyapp", RewardJobID: "job-1", TemplateID: "tpl-1", TemplateCode: "summer", VersionNo: 3,
BoardType: domain.BoardTypeDaily, PeriodKey: "2026-07-14", UserID: 1001, ResourceGroupID: 81,
WalletCommandID: "wallet-stable", RewardSnapshot: domain.RewardSnapshot{SnapshotID: "rgs-1", SnapshotHash: strings.Repeat("d", 64)},
}
repository := &settlementLeaseRepository{periods: []domain.RankPeriod{periodDaily, periodTotal}, jobs: []domain.RewardJob{job}}
wallet := &sequenceRuntimeWallet{}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, wallet)
ctx := appcode.WithContext(context.Background(), "hyapp")
claimed, processed, success, failure, hasMore, err := svc.ProcessRuntimeSettlementBatch(ctx, "run-77", "node-a", 2, 30*time.Second)
if err != nil || claimed != 1 || processed != 1 || success != 1 || failure != 0 || !hasMore {
t.Fatalf("settlement counters = (%d,%d,%d,%d,%v,%v)", claimed, processed, success, failure, hasMore, err)
}
if repository.periodOwner != "node-a:run-77" || repository.jobOwner != repository.periodOwner || len(repository.prepared) != 2 ||
len(repository.granted) != 1 || len(repository.failed) != 0 || len(wallet.requests) != 1 {
t.Fatalf("bounded settlement state mismatch: repository=%+v wallet=%+v", repository, wallet.requests)
}
for _, owner := range repository.leaseOwners {
if owner != repository.jobOwner {
t.Fatalf("job completion escaped lease owner: owners=%v job_owner=%q", repository.leaseOwners, repository.jobOwner)
}
}
if len(repository.finished) != 3 { // two post-materialization checks plus one post-job finalization
t.Fatalf("period finalization checks = %v", repository.finished)
}
if activityTemplateRewardLeaseOwner("node-a", "old-run") == activityTemplateRewardLeaseOwner("node-a", "new-run") {
t.Fatal("settlement invocations on one node must have distinct fencing owners")
}
}
func TestSettlementHasMoreIgnoresFiftyBlockedPeriodsAndDoesNotStarvePending(t *testing.T) {
// Repository discovery represents the production SQL result after fifty older settling periods with
// dead/backoff jobs were filtered out: only the later pending period is actionable.
pending := domain.RankPeriod{
AppCode: "hyapp", TemplateID: "pending-after-50-blocked", TemplateCode: "summer", VersionNo: 3,
BoardType: domain.BoardTypeTotal, PeriodKey: "total",
}
repository := &settlementLeaseRepository{periods: []domain.RankPeriod{pending}}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, &sequenceRuntimeWallet{})
ctx := appcode.WithContext(context.Background(), "hyapp")
claimed, processed, success, failure, hasMore, err := svc.ProcessRuntimeSettlementBatch(
ctx, "run-no-hot-loop", "node-a", 50, time.Minute,
)
if err != nil || claimed != 0 || processed != 0 || success != 0 || failure != 0 {
t.Fatalf("settlement counters = (%d,%d,%d,%d,%v): %v", claimed, processed, success, failure, hasMore, err)
}
if hasMore {
t.Fatal("blocked settling periods must not keep has_more true after the sole pending period is processed")
}
if len(repository.prepared) != 1 || repository.prepared[0] != "total:total" {
t.Fatalf("later pending period was starved: prepared=%v", repository.prepared)
}
}
func (f *captureRuntimeNotice) CreateActivityNotice(_ context.Context, cmd messageservice.NoticeCommand) (messageservice.NoticeResult, error) {
f.commands = append(f.commands, cmd)
return messageservice.NoticeResult{Created: true}, nil
}
func TestRetryRewardJobReplaysStableWalletCommandAndCreatesIdempotentWinnerNotice(t *testing.T) {
job := domain.RewardJob{
RewardJobID: "reward-job-1", TemplateID: "acttpl_1", TemplateCode: "summer", VersionNo: 3,
BoardType: domain.BoardTypeTotal, PeriodKey: "total", RankNo: 2, UserID: 88001, ResourceGroupID: 77,
WalletCommandID: "activity-template-rank:stable-command",
RewardSnapshot: domain.RewardSnapshot{SnapshotID: "rgs_rank", SnapshotHash: strings.Repeat("a", 64), Name: "Champion reward"},
}
repository := &retryRewardRepository{job: job}
wallet := &sequenceRuntimeWallet{errors: []error{errors.New("wallet timeout"), nil}}
notices := &captureRuntimeNotice{}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, wallet)
svc.BindNotice(notices)
ctx := appcode.WithContext(context.Background(), "hyapp")
if _, err := svc.RetryRewardJob(ctx, job.TemplateID, job.RewardJobID, 90001); err == nil {
t.Fatal("first retry must expose wallet failure")
}
got, err := svc.RetryRewardJob(ctx, job.TemplateID, job.RewardJobID, 90001)
if err != nil {
t.Fatalf("second RetryRewardJob() error = %v", err)
}
if len(wallet.requests) != 2 || wallet.requests[0].GetCommandId() != job.WalletCommandID || wallet.requests[1].GetCommandId() != job.WalletCommandID {
t.Fatalf("wallet commands are not stable: %+v", wallet.requests)
}
if got.Status != domain.ClaimStatusGranted || got.WalletGrantID != "wallet-grant-1" || len(repository.markedFailed) != 1 || len(repository.markedGranted) != 1 {
t.Fatalf("retry result=%+v repository failed=%v granted=%v", got, repository.markedFailed, repository.markedGranted)
}
if len(repository.leaseOwners) != 7 || repository.leaseOwners[0] == "" ||
repository.leaseOwners[0] != repository.leaseOwners[1] || repository.leaseOwners[1] != repository.leaseOwners[2] ||
repository.leaseOwners[3] == repository.leaseOwners[0] || repository.leaseOwners[3] != repository.leaseOwners[4] ||
repository.leaseOwners[4] != repository.leaseOwners[5] || repository.leaseOwners[5] != repository.leaseOwners[6] {
t.Fatalf("manual retry lease fencing mismatch: %+v", repository.leaseOwners)
}
if len(notices.commands) != 1 || notices.commands[0].ProducerEventID != job.RewardJobID+":granted" ||
notices.commands[0].ActionType != "activity_detail" || notices.commands[0].ActionParam != "board_type=total&period_key=total&template_code=summer&version_no=3" {
t.Fatalf("winner notice is not stable/readable: %+v", notices.commands)
}
notice := notices.commands[0]
if notice.Title != "activity_template.rank_reward.title" || notice.Summary != "activity_template.rank_reward.summary" ||
strings.Contains(notice.Title, "Daily leaderboard") || notice.Metadata["content_key"] != "activity_template.rank_reward_granted" ||
notice.Metadata["template_code"] != "summer" || notice.Metadata["board_type"] != domain.BoardTypeTotal ||
notice.Metadata["period_key"] != "total" || notice.Metadata["reward_name"] != "Champion reward" {
t.Fatalf("winner notice must use localization keys and immutable metadata: %+v", notice)
}
}
func TestTaskClaimWalletFailureRemainsRetryableAfterActivityEnds(t *testing.T) {
taskDay := "2026-07-14"
endedAt := time.Date(2026, 7, 14, 23, 59, 0, 0, time.UTC)
runtime := domain.Runtime{
Template: domain.Template{TemplateID: "acttpl_1", TemplateCode: "summer", EndMS: endedAt.UnixMilli()},
VersionNo: 3, RuntimeFromMS: endedAt.Add(-24 * time.Hour).UnixMilli(), RuntimeToMS: endedAt.UnixMilli(),
}
claim := domain.TaskClaim{
ClaimID: "task-claim-1", TemplateID: runtime.Template.TemplateID, TemplateCode: runtime.Template.TemplateCode,
VersionNo: runtime.VersionNo, TaskDay: taskDay, TaskKey: "gift-10", UserID: 77001,
RewardResourceGroupID: 88, WalletCommandID: "activity-template-task:stable-command",
RewardSnapshot: domain.RewardSnapshot{SnapshotID: "rgs_task", SnapshotHash: strings.Repeat("b", 64)},
}
repository := &retryRewardRepository{taskRuntime: runtime, taskClaim: claim}
wallet := &sequenceRuntimeWallet{errors: []error{errors.New("wallet timeout"), nil}}
svc := New(&fakeRepository{}, nil, nil)
svc.BindRuntime(repository, wallet)
svc.now = func() time.Time { return endedAt.Add(2 * time.Hour) }
command := domain.ClaimCommand{
UserID: claim.UserID, RegionID: 6, TemplateCode: claim.TemplateCode, VersionNo: claim.VersionNo, TaskDay: taskDay,
TaskKey: claim.TaskKey, CommandID: "client-command-1",
}
ctx := appcode.WithContext(context.Background(), "hyapp")
if _, err := svc.ClaimRuntimeTaskReward(ctx, command); err == nil {
t.Fatal("first claim must expose wallet failure")
}
got, err := svc.ClaimRuntimeTaskReward(ctx, command)
if err != nil {
t.Fatalf("retry after activity end error = %v", err)
}
if repository.taskVersionNo != claim.VersionNo || repository.taskClaimCalls != 2 || len(repository.taskFailed) != 1 || len(repository.taskGranted) != 1 || got.Status != domain.ClaimStatusGranted {
t.Fatalf("task compensation result=%+v repository=%+v", got, repository)
}
if len(wallet.requests) != 2 || wallet.requests[0].GetCommandId() != claim.WalletCommandID || wallet.requests[1].GetCommandId() != claim.WalletCommandID {
t.Fatalf("task wallet command changed across retries: %+v", wallet.requests)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,524 @@
package activitytemplate
import (
"context"
"crypto/sha256"
"encoding/hex"
"testing"
"time"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
type fakeRepository struct {
current domain.Template
versions map[int64]domain.Version
listedVersions []domain.Version
updateCalls int
statusCalls int
createCalls int
}
func (f *fakeRepository) ListActivityTemplates(context.Context, domain.ListQuery) ([]domain.Summary, int64, error) {
return nil, 0, nil
}
func (f *fakeRepository) GetActivityTemplate(_ context.Context, templateID string) (domain.Template, error) {
if f.current.TemplateID != templateID {
return domain.Template{}, xerr.New(xerr.NotFound, "activity template not found")
}
return f.current, nil
}
func (f *fakeRepository) CreateActivityTemplate(_ context.Context, item domain.Template, operatorAdminID int64, nowMS int64) (domain.Template, error) {
f.createCalls++
item.TemplateID = "acttpl_created"
item.Status = domain.StatusDraft
item.Revision = 1
item.CreatedByAdminID = operatorAdminID
item.UpdatedByAdminID = operatorAdminID
item.CreatedAtMS = nowMS
item.UpdatedAtMS = nowMS
f.current = item
return item, nil
}
func (f *fakeRepository) UpdateActivityTemplate(_ context.Context, command domain.UpdateCommand, nowMS int64) (domain.Template, error) {
f.updateCalls++
item := command.Template
item.Revision = command.ExpectedRevision + 1
item.UpdatedByAdminID = command.OperatorAdminID
item.UpdatedAtMS = nowMS
f.current = item
return item, nil
}
func (f *fakeRepository) SetActivityTemplateStatus(_ context.Context, command domain.StatusCommand, nowMS int64) (domain.Template, error) {
f.statusCalls++
item := f.current
item.Status = command.Status
item.Revision++
item.UpdatedByAdminID = command.OperatorAdminID
item.UpdatedAtMS = nowMS
if command.Status == domain.StatusPublished {
item.PublishedVersion++
item.PublishedByAdminID = command.OperatorAdminID
item.PublishedAtMS = nowMS
}
f.current = item
return item, nil
}
func (f *fakeRepository) ListActivityTemplateVersions(context.Context, string, int32, int32) ([]domain.Version, int64, error) {
return append([]domain.Version(nil), f.listedVersions...), int64(len(f.listedVersions)), nil
}
func (f *fakeRepository) GetActivityTemplateVersion(_ context.Context, templateID string, versionNo int64) (domain.Version, error) {
item, ok := f.versions[versionNo]
if !ok || item.TemplateID != templateID {
return domain.Version{}, xerr.New(xerr.NotFound, "activity template version not found")
}
return item, nil
}
type fakeCatalog struct {
gift *walletv1.GiftConfig
groups map[int64]*walletv1.ResourceGroup
lastGift *walletv1.ListGiftConfigsRequest
pins []*walletv1.PinResourceGroupSnapshotRequest
}
type fakeRegionSource struct {
regionIDs []int64
err error
}
func (f fakeRegionSource) ListActiveRegionIDs(context.Context) ([]int64, error) {
return append([]int64(nil), f.regionIDs...), f.err
}
func (f *fakeCatalog) ListGiftConfigs(_ context.Context, req *walletv1.ListGiftConfigsRequest, _ ...grpc.CallOption) (*walletv1.ListGiftConfigsResponse, error) {
f.lastGift = req
if f.gift == nil || f.gift.GetGiftId() != req.GetKeyword() {
return &walletv1.ListGiftConfigsResponse{}, nil
}
return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{f.gift}, Total: 1}, nil
}
func (f *fakeCatalog) GetResourceGroup(_ context.Context, req *walletv1.GetResourceGroupRequest, _ ...grpc.CallOption) (*walletv1.GetResourceGroupResponse, error) {
group := f.groups[req.GetGroupId()]
if group == nil {
return nil, xerr.New(xerr.NotFound, "resource group not found")
}
return &walletv1.GetResourceGroupResponse{Group: group, SourceContentHash: fakeResourceGroupHash(group)}, nil
}
func (f *fakeCatalog) PinResourceGroupSnapshot(_ context.Context, req *walletv1.PinResourceGroupSnapshotRequest, _ ...grpc.CallOption) (*walletv1.PinResourceGroupSnapshotResponse, error) {
f.pins = append(f.pins, req)
group := f.groups[req.GetGroupId()]
if group == nil {
return nil, xerr.New(xerr.NotFound, "resource group not found")
}
hash := fakeResourceGroupHash(group)
if req.GetExpectedSourceContentHash() != hash || req.GetExpectedGroupUpdatedAtMs() != group.GetUpdatedAtMs() {
return nil, xerr.New(xerr.Conflict, "resource group content changed")
}
return &walletv1.PinResourceGroupSnapshotResponse{Snapshot: &walletv1.ResourceGroupSnapshot{
AppCode: req.GetAppCode(), SnapshotId: "rgs_" + hash[:24], PinKey: req.GetPinKey(), SourceGroupId: group.GetGroupId(),
VersionNo: 1, SnapshotHash: hash, Group: group, CreatedByUserId: req.GetOperatorUserId(), CreatedAtMs: 1,
SourceGroupUpdatedAtMs: group.GetUpdatedAtMs(), SourceContentHash: hash,
}}, nil
}
func fakeResourceGroupHash(group *walletv1.ResourceGroup) string {
body, _ := proto.MarshalOptions{Deterministic: true}.Marshal(group)
sum := sha256.Sum256(body)
return hex.EncodeToString(sum[:])
}
func TestLifecycleStatusUsesUTCLeftClosedRightOpenBoundary(t *testing.T) {
const startMS = int64(10_000)
const endMS = int64(20_000)
tests := []struct {
name string
now int64
want string
}{
{name: "before start", now: startMS - 1, want: domain.LifecycleScheduled},
{name: "at start", now: startMS, want: domain.LifecycleOngoing},
{name: "before end", now: endMS - 1, want: domain.LifecycleOngoing},
{name: "at end", now: endMS, want: domain.LifecycleEnded},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := lifecycleStatus(domain.StatusPublished, startMS, endMS, tt.now); got != tt.want {
t.Fatalf("lifecycleStatus() = %q, want %q", got, tt.want)
}
})
}
}
func TestListVersionsUsesActualRuntimeWindowAfterDisableAndRepublish(t *testing.T) {
const (
startMS = int64(100)
endMS = int64(1_000)
nowMS = int64(700)
)
snapshot := domain.Template{
TemplateID: "acttpl_republished", TemplateCode: "summer", Status: domain.StatusPublished,
StartMS: startMS, EndMS: endMS,
}
repository := &fakeRepository{listedVersions: []domain.Version{
{TemplateID: snapshot.TemplateID, VersionNo: 2, Snapshot: snapshot, RuntimeFromMS: 600},
{TemplateID: snapshot.TemplateID, VersionNo: 1, Snapshot: snapshot, RuntimeFromMS: startMS, RuntimeToMS: 500},
}}
svc := New(repository, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
versions, total, err := svc.ListVersions(context.Background(), snapshot.TemplateID, 1, 20)
if err != nil {
t.Fatalf("ListVersions() error = %v", err)
}
if total != 2 || len(versions) != 2 {
t.Fatalf("ListVersions() total/items = %d/%d", total, len(versions))
}
if versions[0].VersionNo != 2 || versions[0].Snapshot.LifecycleStatus != domain.LifecycleOngoing || versions[0].RuntimeFromMS != 600 || versions[0].RuntimeToMS != 0 {
t.Fatalf("republished v2 lifecycle/window = %+v", versions[0])
}
if versions[1].VersionNo != 1 || versions[1].Snapshot.LifecycleStatus != domain.LifecycleDisabled || versions[1].RuntimeFromMS != startMS || versions[1].RuntimeToMS != 500 {
t.Fatalf("disabled v1 lifecycle/window = %+v", versions[1])
}
}
func TestRuntimeLifecycleStatusUsesActualWindowBoundaries(t *testing.T) {
item := domain.Template{Status: domain.StatusPublished, StartMS: 100, EndMS: 1_000}
tests := []struct {
name string
runtimeFromMS int64
runtimeToMS int64
nowMS int64
want string
}{
{name: "scheduled", runtimeFromMS: 200, nowMS: 199, want: domain.LifecycleScheduled},
{name: "ongoing at runtime start", runtimeFromMS: 200, nowMS: 200, want: domain.LifecycleOngoing},
{name: "disabled at exclusive runtime end", runtimeFromMS: 200, runtimeToMS: 500, nowMS: 500, want: domain.LifecycleDisabled},
{name: "disabled remains historical state", runtimeFromMS: 200, runtimeToMS: 500, nowMS: 1_500, want: domain.LifecycleDisabled},
{name: "naturally ended", runtimeFromMS: 200, nowMS: 1_000, want: domain.LifecycleEnded},
{name: "disable after planned end stays ended", runtimeFromMS: 200, runtimeToMS: 1_200, nowMS: 1_200, want: domain.LifecycleEnded},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RuntimeLifecycleStatus(item, tt.runtimeFromMS, tt.runtimeToMS, tt.nowMS); got != tt.want {
t.Fatalf("RuntimeLifecycleStatus() = %q, want %q", got, tt.want)
}
})
}
}
func TestListRejectsNegativeTimeFilter(t *testing.T) {
svc := New(&fakeRepository{}, nil, nil)
_, _, err := svc.List(context.Background(), domain.ListQuery{StartMS: -1})
if !xerr.IsCode(err, xerr.InvalidArgument) {
t.Fatalf("List() error = %v, want invalid argument", err)
}
}
func TestValidatePublishRejectsOverlappingRankRangesAndNonObjectDisplayConfig(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
item.RankRewards = append(item.RankRewards, domain.RankReward{
BoardType: domain.BoardTypeDaily, RankFrom: 3, RankTo: 5, ResourceGroupID: 102, SortOrder: 2,
})
item.DisplayConfigJSON = "null"
svc := New(&fakeRepository{}, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
issues, err := svc.Validate(context.Background(), item, true)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if !hasIssue(issues, "rank_rewards", "overlap") {
t.Fatalf("expected rank overlap issue, got %+v", issues)
}
if !hasIssue(issues, "display_config_json", "invalid_json") {
t.Fatalf("expected JSON object issue, got %+v", issues)
}
}
func TestValidateRejectsMultipleDailyVisitTasks(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
item.Tasks = append(item.Tasks, domain.Task{
TaskKey: "daily_visit_bonus", TaskType: domain.TaskTypeDailyVisit,
TargetValue: 1, RewardResourceGroupID: 101, SortOrder: 2,
})
svc := New(&fakeRepository{}, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
issues, err := svc.Validate(context.Background(), item, false)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if !hasIssue(issues, "tasks", "duplicate_daily_visit") {
t.Fatalf("expected duplicate daily_visit issue, got %+v", issues)
}
}
func TestSetStatusPublishesOnlyAfterOwnerReferencesValidate(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
repository := &fakeRepository{current: item}
catalog := publishableCatalog(item)
svc := New(repository, catalog, fakeRegionSource{regionIDs: []int64{1}})
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
ctx := appcode.WithContext(context.Background(), "hyapp")
got, err := svc.SetStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished,
ExpectedRevision: item.Revision, OperatorAdminID: 90001,
})
if err != nil {
t.Fatalf("SetStatus() error = %v", err)
}
if got.Status != domain.StatusPublished || got.PublishedVersion != 1 || got.Revision != item.Revision+1 {
t.Fatalf("published template metadata = %+v", got)
}
if repository.statusCalls != 1 {
t.Fatalf("status repository calls = %d, want 1", repository.statusCalls)
}
if catalog.lastGift == nil || catalog.lastGift.GetStatus() != "active" || catalog.lastGift.GetActiveOnly() {
t.Fatalf("gift lookup must load active owner config without current-time-only filtering: %+v", catalog.lastGift)
}
if len(catalog.pins) != 3 {
t.Fatalf("publish pin calls = %d, want 3", len(catalog.pins))
}
for _, pin := range catalog.pins {
if pin.GetRequiredAllRegions() || len(pin.GetRequiredRegionIds()) != 1 || pin.GetRequiredRegionIds()[0] != 1 {
t.Fatalf("targeted activity pin omitted required region coverage: %+v", pin)
}
}
}
func TestSetStatusAllRegionsRequiresGlobalRewardGiftCoverageAtWalletPin(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
item.AllRegions = true
item.RegionIDs = nil
repository := &fakeRepository{current: item}
catalog := publishableCatalog(item)
svc := New(repository, catalog, fakeRegionSource{regionIDs: []int64{1, 2}})
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
if _, err := svc.SetStatus(appcode.WithContext(context.Background(), "hyapp"), domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished,
ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}); err != nil {
t.Fatalf("publish all-regions template: %v", err)
}
if len(catalog.pins) != 3 {
t.Fatalf("all-regions publish pin calls = %d, want 3", len(catalog.pins))
}
for _, pin := range catalog.pins {
if !pin.GetRequiredAllRegions() || len(pin.GetRequiredRegionIds()) != 0 {
t.Fatalf("all-regions activity must require global reward gift coverage: %+v", pin)
}
}
}
func TestValidatePublishRejectsGiftThatExpiresDuringActivity(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
catalog := publishableCatalog(item)
catalog.gift.EffectiveToMs = item.EndMS - 1
svc := New(&fakeRepository{}, catalog, fakeRegionSource{regionIDs: []int64{1}})
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
issues, err := svc.Validate(context.Background(), item, true)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if !hasIssue(issues, "gifts", "gift_period_mismatch") {
t.Fatalf("expected gift period issue, got %+v", issues)
}
}
func TestValidatePublishRejectsInactiveTargetRegion(t *testing.T) {
nowMS := int64(1_800_000_000_000)
item := publishableTemplate(nowMS)
svc := New(&fakeRepository{}, publishableCatalog(item), fakeRegionSource{regionIDs: []int64{2}})
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
issues, err := svc.Validate(context.Background(), item, true)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if !hasIssue(issues, "region_ids", "region_not_active") {
t.Fatalf("expected inactive region issue, got %+v", issues)
}
}
func TestUpdateRejectsStaleRevisionBeforeWriting(t *testing.T) {
nowMS := int64(1_800_000_000_000)
current := publishableTemplate(nowMS)
current.Status = domain.StatusDisabled
repository := &fakeRepository{current: current}
svc := New(repository, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
_, err := svc.Update(context.Background(), domain.UpdateCommand{
Template: current, ExpectedRevision: current.Revision - 1, OperatorAdminID: 90001,
})
if !xerr.IsCode(err, xerr.Conflict) {
t.Fatalf("Update() error = %v, want conflict", err)
}
if repository.updateCalls != 0 {
t.Fatalf("stale update wrote repository %d times", repository.updateCalls)
}
}
func TestUpdateRejectsTemplateCodeChangeAfterCreate(t *testing.T) {
nowMS := int64(1_800_000_000_000)
current := publishableTemplate(nowMS)
current.Status = domain.StatusDisabled
repository := &fakeRepository{current: current}
svc := New(repository, nil, nil)
changed := current
changed.TemplateCode = "replacement_code"
_, err := svc.Update(context.Background(), domain.UpdateCommand{
Template: changed, ExpectedRevision: current.Revision, OperatorAdminID: 90001,
})
if !xerr.IsCode(err, xerr.Conflict) || repository.updateCalls != 0 {
t.Fatalf("code mutation error=%v update_calls=%d", err, repository.updateCalls)
}
}
func TestDeleteRequiresScheduledOrOngoingPublishedTemplateToBeDisabledFirst(t *testing.T) {
nowMS := int64(1_800_000_000_000)
current := publishableTemplate(nowMS)
current.Status = domain.StatusPublished
repository := &fakeRepository{current: current}
svc := New(repository, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
_, _, err := svc.Delete(context.Background(), domain.StatusCommand{
TemplateID: current.TemplateID, ExpectedRevision: current.Revision, OperatorAdminID: 90001,
})
if !xerr.IsCode(err, xerr.Conflict) {
t.Fatalf("Delete() error = %v, want conflict", err)
}
if repository.statusCalls != 0 {
t.Fatalf("scheduled template was archived %d times", repository.statusCalls)
}
}
func TestDeleteAllowsEndedPublishedTemplateToArchive(t *testing.T) {
nowMS := int64(1_800_000_000_000)
current := publishableTemplate(nowMS)
current.Status = domain.StatusPublished
current.StartMS = nowMS - 2_000
current.EndMS = nowMS - 1_000
repository := &fakeRepository{current: current}
svc := New(repository, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
got, archived, err := svc.Delete(context.Background(), domain.StatusCommand{
TemplateID: current.TemplateID, ExpectedRevision: current.Revision, OperatorAdminID: 90001,
})
if err != nil {
t.Fatalf("Delete() error = %v", err)
}
if !archived || got.Status != domain.StatusArchived || got.LifecycleStatus != domain.LifecycleArchived {
t.Fatalf("Delete() = archived:%v template:%+v", archived, got)
}
}
func TestClonePreservesConfigurationButClearsRuntimeIdentityAndSchedule(t *testing.T) {
nowMS := int64(1_800_000_000_000)
source := publishableTemplate(nowMS)
source.Status = domain.StatusDisabled
source.PublishedVersion = 3
source.PublishedAtMS = nowMS - 10_000
repository := &fakeRepository{current: source}
svc := New(repository, nil, nil)
svc.now = func() time.Time { return time.UnixMilli(nowMS) }
got, err := svc.Clone(context.Background(), domain.CloneCommand{
SourceTemplateID: source.TemplateID, TemplateCode: "gift_challenge_copy", Name: "Gift Challenge Copy", OperatorAdminID: 90002,
})
if err != nil {
t.Fatalf("Clone() error = %v", err)
}
if got.TemplateID == source.TemplateID || got.TemplateCode != "gift_challenge_copy" || got.Status != domain.StatusDraft {
t.Fatalf("clone identity/status = %+v", got)
}
if got.StartMS != 0 || got.EndMS != 0 || got.PublishedVersion != 0 || got.PublishedAtMS != 0 {
t.Fatalf("clone leaked runtime metadata = %+v", got)
}
if len(got.RegionIDs) != 1 || got.RegionIDs[0] != source.RegionIDs[0] ||
len(got.Tasks) != 1 || got.Tasks[0].RewardResourceGroupID != source.Tasks[0].RewardResourceGroupID ||
len(got.RankRewards) != len(source.RankRewards) {
t.Fatalf("clone did not preserve configuration: %+v", got)
}
}
func publishableTemplate(nowMS int64) domain.Template {
return domain.Template{
TemplateID: "acttpl_source", TemplateCode: "gift_challenge_2026", Name: "Gift Challenge 2026",
ActivityType: domain.ActivityTypeGiftChallenge, Status: domain.StatusDraft, Revision: 7,
StartMS: nowMS + 60_000, EndMS: nowMS + int64((7*24*time.Hour)/time.Millisecond),
AllRegions: false, RegionIDs: []int64{1},
Locales: []domain.Locale{{Locale: "en", Title: "Gift Challenge", Rules: "Send gifts and complete tasks."}},
Gifts: []domain.Gift{{
GiftID: "gift_rose", SortOrder: 1, Name: "Rose", IconURL: "https://cdn.example.com/gifts/rose.png", CoinPrice: 100, PriceVersion: "v3",
}},
Tasks: []domain.Task{{
TaskKey: "daily_visit", TaskType: domain.TaskTypeDailyVisit, TargetValue: 1, RewardResourceGroupID: 101, SortOrder: 1,
}},
DailyLeaderboardSize: 100,
TotalLeaderboardSize: 100,
RankRewards: []domain.RankReward{
{BoardType: domain.BoardTypeDaily, RankFrom: 1, RankTo: 3, ResourceGroupID: 102, SortOrder: 1},
{BoardType: domain.BoardTypeTotal, RankFrom: 1, RankTo: 3, ResourceGroupID: 103, SortOrder: 1},
},
DisplayConfigJSON: `{}`,
}
}
func publishableCatalog(item domain.Template) *fakeCatalog {
groups := make(map[int64]*walletv1.ResourceGroup)
for _, groupID := range []int64{101, 102, 103} {
groups[groupID] = &walletv1.ResourceGroup{
GroupId: groupID, Status: "active", Name: "Reward", UpdatedAtMs: 1_800_000_000_001,
Items: []*walletv1.ResourceGroupItem{{
GroupId: groupID, ResourceId: groupID + 1000, Quantity: 1, ItemType: "resource",
Resource: &walletv1.Resource{ResourceId: groupID + 1000, ResourceType: "badge", Name: "Badge", Status: "active", Grantable: true, GrantStrategy: "new_entitlement"},
}},
}
}
return &fakeCatalog{
gift: &walletv1.GiftConfig{
GiftId: item.Gifts[0].GiftID, Status: "active", Name: item.Gifts[0].Name,
CoinPrice: item.Gifts[0].CoinPrice, PriceVersion: item.Gifts[0].PriceVersion,
RegionIds: []int64{0},
Resource: &walletv1.Resource{
Status: "active", PreviewUrl: item.Gifts[0].IconURL, AssetUrl: "https://cdn.example.com/gifts/rose-animation.svga",
},
},
groups: groups,
}
}
func hasIssue(issues []domain.ValidationIssue, field string, code string) bool {
for _, issue := range issues {
if issue.Field == field && issue.Code == code {
return true
}
}
return false
}

View File

@ -0,0 +1,787 @@
package mysql
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"strings"
mysqlDriver "github.com/go-sql-driver/mysql"
"hyapp/pkg/appcode"
"hyapp/pkg/idgen"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
const activityTemplateSelect = `
SELECT app_code, template_id, template_code, name, activity_type, status, start_ms, end_ms, all_regions,
daily_leaderboard_size, total_leaderboard_size, CAST(display_config_json AS CHAR), revision, published_version,
created_by_admin_id, updated_by_admin_id, published_by_admin_id, created_at_ms, updated_at_ms,
published_at_ms, archived_at_ms
FROM activity_templates`
type activityTemplateQuerier interface {
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
type activityTemplateScanner interface {
Scan(dest ...any) error
}
// ListActivityTemplates 查询轻量列表投影,只额外附加区域 ID大段规则、任务和素材只在详情接口读取。
func (r *Repository) ListActivityTemplates(ctx context.Context, query domain.ListQuery) ([]domain.Summary, int64, error) {
if r == nil || r.db == nil {
return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
where, args := activityTemplateListWhere(ctx, query)
var total int64
if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM activity_templates t `+where, args...).Scan(&total); err != nil {
return nil, 0, err
}
pageSize := normalizePageSize(query.PageSize)
page := query.Page
if page <= 0 {
page = 1
}
rows, err := r.db.QueryContext(ctx, `
SELECT t.app_code, t.template_id, t.template_code, t.name, t.activity_type, t.status, t.start_ms, t.end_ms,
t.all_regions, t.revision, t.published_version, t.created_by_admin_id, t.updated_by_admin_id,
t.created_at_ms, t.updated_at_ms
FROM activity_templates t `+where+`
ORDER BY t.updated_at_ms DESC, t.template_id DESC
LIMIT ? OFFSET ?`, append(args, int(pageSize), int((page-1)*pageSize))...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
items := make([]domain.Summary, 0)
for rows.Next() {
var item domain.Summary
if err := rows.Scan(
&item.AppCode, &item.TemplateID, &item.TemplateCode, &item.Name, &item.ActivityType, &item.Status,
&item.StartMS, &item.EndMS, &item.AllRegions, &item.Revision, &item.PublishedVersion,
&item.CreatedByAdminID, &item.UpdatedByAdminID, &item.CreatedAtMS, &item.UpdatedAtMS,
); err != nil {
return nil, 0, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, 0, err
}
if err := attachActivityTemplateSummaryRegions(ctx, r.db, items); err != nil {
return nil, 0, err
}
return items, total, nil
}
func (r *Repository) GetActivityTemplate(ctx context.Context, templateID string) (domain.Template, error) {
if r == nil || r.db == nil {
return domain.Template{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
return getActivityTemplateAggregate(ctx, r.db, appcode.FromContext(ctx), strings.TrimSpace(templateID), false)
}
func (r *Repository) CreateActivityTemplate(ctx context.Context, item domain.Template, operatorAdminID int64, nowMS int64) (domain.Template, error) {
if r == nil || r.db == nil {
return domain.Template{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.Template{}, err
}
defer tx.Rollback()
item.AppCode = appcode.FromContext(ctx)
item.TemplateID = idgen.New("acttpl")
item.Status = domain.StatusDraft
item.Revision = 1
item.PublishedVersion = 0
item.CreatedByAdminID = operatorAdminID
item.UpdatedByAdminID = operatorAdminID
item.PublishedByAdminID = 0
item.CreatedAtMS = nowMS
item.UpdatedAtMS = nowMS
item.PublishedAtMS = 0
item.ArchivedAtMS = 0
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_templates (
app_code, template_id, template_code, name, activity_type, status, start_ms, end_ms, all_regions,
daily_leaderboard_size, total_leaderboard_size, display_config_json, revision, published_version,
created_by_admin_id, updated_by_admin_id, published_by_admin_id, created_at_ms, updated_at_ms, published_at_ms, archived_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.AppCode, item.TemplateID, item.TemplateCode, item.Name, item.ActivityType, item.Status, item.StartMS, item.EndMS, item.AllRegions,
item.DailyLeaderboardSize, item.TotalLeaderboardSize, item.DisplayConfigJSON, item.Revision, item.PublishedVersion,
item.CreatedByAdminID, item.UpdatedByAdminID, item.PublishedByAdminID, item.CreatedAtMS, item.UpdatedAtMS, item.PublishedAtMS, item.ArchivedAtMS,
); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
if err := replaceActivityTemplateChildren(ctx, tx, item, nowMS); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
if err := tx.Commit(); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
return r.GetActivityTemplate(ctx, item.TemplateID)
}
func (r *Repository) UpdateActivityTemplate(ctx context.Context, command domain.UpdateCommand, nowMS int64) (domain.Template, error) {
if r == nil || r.db == nil {
return domain.Template{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.Template{}, err
}
defer tx.Rollback()
appCode := appcode.FromContext(ctx)
current, err := getActivityTemplateAggregate(ctx, tx, appCode, command.Template.TemplateID, true)
if err != nil {
return domain.Template{}, err
}
if current.Revision != command.ExpectedRevision {
return domain.Template{}, xerr.New(xerr.Conflict, "activity template revision changed")
}
// Repository 再做一次身份防线,避免未来新增调用方绕过 service 直接改写 deep-link/runtime code。
if command.Template.TemplateCode != current.TemplateCode {
return domain.Template{}, xerr.New(xerr.Conflict, "activity template_code is immutable")
}
if current.Status == domain.StatusArchived {
return domain.Template{}, xerr.New(xerr.Conflict, "archived activity template is read-only")
}
item := command.Template
item.AppCode = appCode
item.TemplateID = current.TemplateID
item.Status = current.Status
item.Revision = current.Revision + 1
item.PublishedVersion = current.PublishedVersion
item.CreatedByAdminID = current.CreatedByAdminID
item.CreatedAtMS = current.CreatedAtMS
item.UpdatedByAdminID = command.OperatorAdminID
item.UpdatedAtMS = nowMS
item.PublishedByAdminID = current.PublishedByAdminID
item.PublishedAtMS = current.PublishedAtMS
item.ArchivedAtMS = current.ArchivedAtMS
result, err := tx.ExecContext(ctx, `
UPDATE activity_templates
SET name = ?, activity_type = ?, start_ms = ?, end_ms = ?, all_regions = ?,
daily_leaderboard_size = ?, total_leaderboard_size = ?, display_config_json = CAST(? AS JSON),
revision = ?, updated_by_admin_id = ?, updated_at_ms = ?
WHERE app_code = ? AND template_id = ? AND revision = ?`,
item.Name, item.ActivityType, item.StartMS, item.EndMS, item.AllRegions,
item.DailyLeaderboardSize, item.TotalLeaderboardSize, item.DisplayConfigJSON,
item.Revision, item.UpdatedByAdminID, item.UpdatedAtMS,
appCode, item.TemplateID, command.ExpectedRevision)
if err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
affected, _ := result.RowsAffected()
if affected != 1 {
return domain.Template{}, xerr.New(xerr.Conflict, "activity template revision changed")
}
if err := replaceActivityTemplateChildren(ctx, tx, item, nowMS); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
if err := tx.Commit(); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
return r.GetActivityTemplate(ctx, item.TemplateID)
}
func (r *Repository) SetActivityTemplateStatus(ctx context.Context, command domain.StatusCommand, nowMS int64) (domain.Template, error) {
if r == nil || r.db == nil {
return domain.Template{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return domain.Template{}, err
}
defer tx.Rollback()
appCode := appcode.FromContext(ctx)
if command.Status == domain.StatusPublished {
// 同一 App 的发布事务先争用一条稳定互斥行,再读取当前草稿与已发布区间。
// 否则两个互不相同的草稿可以在各自事务快照中都看不到对方,造成区域排期写偏差。
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_publish_locks (app_code, updated_at_ms)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE updated_at_ms = ?`, appCode, nowMS, nowMS); err != nil {
return domain.Template{}, err
}
}
current, err := getActivityTemplateAggregate(ctx, tx, appCode, command.TemplateID, true)
if err != nil {
return domain.Template{}, err
}
if current.Revision != command.ExpectedRevision {
return domain.Template{}, xerr.New(xerr.Conflict, "activity template revision changed")
}
if current.Status == domain.StatusArchived {
return domain.Template{}, xerr.New(xerr.Conflict, "archived activity template is read-only")
}
if command.Status == domain.StatusPublished {
// 发布冲突检查与状态写入必须在同一个事务内。all_regions 与任何区域活动冲突;
// 定向活动只在区域集合有交集且 UTC [start_ms,end_ms) 相交时冲突。
if err := ensureNoPublishedActivityTemplateOverlap(ctx, tx, current); err != nil {
return domain.Template{}, err
}
}
nextRevision := current.Revision + 1
nextPublishedVersion := current.PublishedVersion
nextPublishedBy := current.PublishedByAdminID
nextPublishedAt := current.PublishedAtMS
nextArchivedAt := current.ArchivedAtMS
if command.Status == domain.StatusPublished {
nextPublishedVersion++
nextPublishedBy = command.OperatorAdminID
nextPublishedAt = nowMS
}
if command.Status == domain.StatusArchived {
nextArchivedAt = nowMS
}
result, err := tx.ExecContext(ctx, `
UPDATE activity_templates
SET status = ?, revision = ?, published_version = ?, updated_by_admin_id = ?, updated_at_ms = ?,
published_by_admin_id = ?, published_at_ms = ?, archived_at_ms = ?
WHERE app_code = ? AND template_id = ? AND revision = ?`,
command.Status, nextRevision, nextPublishedVersion, command.OperatorAdminID, nowMS,
nextPublishedBy, nextPublishedAt, nextArchivedAt,
appCode, current.TemplateID, command.ExpectedRevision)
if err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
affected, _ := result.RowsAffected()
if affected != 1 {
return domain.Template{}, xerr.New(xerr.Conflict, "activity template revision changed")
}
if command.Status == domain.StatusPublished {
published, err := getActivityTemplateAggregate(ctx, tx, appCode, current.TemplateID, false)
if err != nil {
return domain.Template{}, err
}
// wallet-owner snapshot IDs are produced immediately before this transaction and keyed by the next
// published version. Only these trusted refs are embedded; mutable edit tables continue storing group IDs.
for index := range published.Tasks {
if published.Tasks[index].RewardResourceGroupID <= 0 {
continue
}
snapshot, ok := command.RewardSnapshots[published.Tasks[index].RewardResourceGroupID]
if !ok || snapshot.SnapshotID == "" {
return domain.Template{}, xerr.New(xerr.Conflict, "activity task reward snapshot is missing")
}
published.Tasks[index].RewardSnapshot = snapshot
}
for index := range published.RankRewards {
snapshot, ok := command.RewardSnapshots[published.RankRewards[index].ResourceGroupID]
if !ok || snapshot.SnapshotID == "" {
return domain.Template{}, xerr.New(xerr.Conflict, "activity rank reward snapshot is missing")
}
published.RankRewards[index].RewardSnapshot = snapshot
}
published.LifecycleStatus = ""
snapshotJSON, err := json.Marshal(published)
if err != nil {
return domain.Template{}, err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_versions (
app_code, template_id, version_no, snapshot_json, published_by_admin_id, published_at_ms
) VALUES (?, ?, ?, CAST(? AS JSON), ?, ?)`,
appCode, current.TemplateID, nextPublishedVersion, string(snapshotJSON), command.OperatorAdminID, nowMS); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
// 运行侧只绑定刚写入的不可变快照;这里和状态/版本在同一事务建启用窗口及 UTC 结算周期,
// 避免发布已成功但 MQ 消费或 cron 永远找不到运行版本的半发布状态。
if err := insertActivityTemplateRuntimeVersion(ctx, tx, published, nextPublishedVersion, nowMS, r.activityTemplateSettlementLatenessMS()); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
} else if command.Status == domain.StatusDisabled {
// 停用只关闭当前发布版本的真实事件窗口,并把未来周期取消、进行中的周期截断为立即可结算。
// 已冻结快照和历史发奖任务不删除,后续重试仍按原 published_version 审计。
if err := closeActivityTemplateRuntimeVersion(ctx, tx, current.TemplateID, current.PublishedVersion, nowMS, r.activityTemplateSettlementLatenessMS()); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
}
if err := tx.Commit(); err != nil {
return domain.Template{}, activityTemplateWriteError(err)
}
return r.GetActivityTemplate(ctx, current.TemplateID)
}
func (r *Repository) ListActivityTemplateVersions(ctx context.Context, templateID string, page int32, pageSize int32) ([]domain.Version, int64, error) {
if r == nil || r.db == nil {
return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
appCode := appcode.FromContext(ctx)
if _, err := getActivityTemplateBase(ctx, r.db, appCode, templateID, false); err != nil {
return nil, 0, err
}
var total int64
if err := r.db.QueryRowContext(ctx, `
SELECT COUNT(*) FROM activity_template_versions WHERE app_code = ? AND template_id = ?`,
appCode, templateID).Scan(&total); err != nil {
return nil, 0, err
}
pageSize = normalizePageSize(pageSize)
if page <= 0 {
page = 1
}
rows, err := r.db.QueryContext(ctx, `
SELECT v.version_no, CAST(v.snapshot_json AS CHAR), v.published_by_admin_id, v.published_at_ms,
COALESCE(rv.runtime_from_ms, 0), COALESCE(rv.runtime_to_ms, 0)
FROM activity_template_versions v
LEFT JOIN activity_template_runtime_versions rv
ON rv.app_code = v.app_code AND rv.template_id = v.template_id AND rv.version_no = v.version_no
WHERE v.app_code = ? AND v.template_id = ?
ORDER BY v.version_no DESC
LIMIT ? OFFSET ?`, appCode, templateID, int(pageSize), int((page-1)*pageSize))
if err != nil {
return nil, 0, err
}
defer rows.Close()
items := make([]domain.Version, 0)
for rows.Next() {
var item domain.Version
var snapshotJSON string
item.TemplateID = templateID
if err := rows.Scan(
&item.VersionNo, &snapshotJSON, &item.PublishedByAdminID, &item.PublishedAtMS,
&item.RuntimeFromMS, &item.RuntimeToMS,
); err != nil {
return nil, 0, err
}
if err := json.Unmarshal([]byte(snapshotJSON), &item.Snapshot); err != nil {
return nil, 0, fmt.Errorf("decode activity template version %d: %w", item.VersionNo, err)
}
items = append(items, item)
}
return items, total, rows.Err()
}
func (r *Repository) GetActivityTemplateVersion(ctx context.Context, templateID string, versionNo int64) (domain.Version, error) {
if r == nil || r.db == nil {
return domain.Version{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
var item domain.Version
var snapshotJSON string
item.TemplateID = strings.TrimSpace(templateID)
err := r.db.QueryRowContext(ctx, `
SELECT v.version_no, CAST(v.snapshot_json AS CHAR), v.published_by_admin_id, v.published_at_ms,
COALESCE(rv.runtime_from_ms, 0), COALESCE(rv.runtime_to_ms, 0)
FROM activity_template_versions v
LEFT JOIN activity_template_runtime_versions rv
ON rv.app_code = v.app_code AND rv.template_id = v.template_id AND rv.version_no = v.version_no
WHERE v.app_code = ? AND v.template_id = ? AND v.version_no = ?`,
appcode.FromContext(ctx), item.TemplateID, versionNo).Scan(
&item.VersionNo, &snapshotJSON, &item.PublishedByAdminID, &item.PublishedAtMS,
&item.RuntimeFromMS, &item.RuntimeToMS)
if errors.Is(err, sql.ErrNoRows) {
return domain.Version{}, xerr.New(xerr.NotFound, "activity template version not found")
}
if err != nil {
return domain.Version{}, err
}
if err := json.Unmarshal([]byte(snapshotJSON), &item.Snapshot); err != nil {
return domain.Version{}, fmt.Errorf("decode activity template version %d: %w", item.VersionNo, err)
}
return item, nil
}
func activityTemplateListWhere(ctx context.Context, query domain.ListQuery) (string, []any) {
parts := []string{"t.app_code = ?"}
args := []any{appcode.FromContext(ctx)}
if query.Status == "" {
parts = append(parts, "t.status <> ?")
args = append(args, domain.StatusArchived)
} else {
switch query.Status {
case domain.LifecycleScheduled:
parts = append(parts, "t.status = ? AND t.start_ms > ?")
args = append(args, domain.StatusPublished, query.NowMS)
case domain.LifecycleOngoing, "active":
parts = append(parts, "t.status = ? AND t.start_ms <= ? AND t.end_ms > ?")
args = append(args, domain.StatusPublished, query.NowMS, query.NowMS)
case domain.LifecycleEnded:
parts = append(parts, "t.status = ? AND t.end_ms <= ?")
args = append(args, domain.StatusPublished, query.NowMS)
default:
parts = append(parts, "t.status = ?")
args = append(args, query.Status)
}
}
if query.Keyword != "" {
parts = append(parts, "(t.template_id LIKE ? OR t.template_code LIKE ? OR t.name LIKE ?)")
keyword := "%" + query.Keyword + "%"
args = append(args, keyword, keyword, keyword)
}
if query.StartMS > 0 || query.EndMS > 0 {
// 未排期草稿的 start_ms/end_ms 均为 0不属于任何时间区间尤其只有 end_ms 时不能把它们误算为相交。
parts = append(parts, "t.start_ms > 0 AND t.end_ms > t.start_ms")
}
if query.StartMS > 0 {
// 区间筛选使用相交语义,不要求活动开始时间落在查询区间内。
parts = append(parts, "t.end_ms > ?")
args = append(args, query.StartMS)
}
if query.EndMS > 0 {
parts = append(parts, "t.start_ms < ?")
args = append(args, query.EndMS)
}
if query.AllRegions != nil {
parts = append(parts, "t.all_regions = ?")
args = append(args, *query.AllRegions)
}
if query.RegionID != nil {
if query.AllRegions != nil && !*query.AllRegions {
parts = append(parts, "EXISTS (SELECT 1 FROM activity_template_regions tr WHERE tr.app_code = t.app_code AND tr.template_id = t.template_id AND tr.region_id = ?)")
} else {
// 未显式限定 all_regions 时,区域筛选返回所有对该区域生效的活动,包括全区活动。
parts = append(parts, "(t.all_regions = TRUE OR EXISTS (SELECT 1 FROM activity_template_regions tr WHERE tr.app_code = t.app_code AND tr.template_id = t.template_id AND tr.region_id = ?))")
}
args = append(args, *query.RegionID)
}
return "WHERE " + strings.Join(parts, " AND "), args
}
func getActivityTemplateAggregate(ctx context.Context, q activityTemplateQuerier, appCode string, templateID string, forUpdate bool) (domain.Template, error) {
item, err := getActivityTemplateBase(ctx, q, appCode, templateID, forUpdate)
if err != nil {
return domain.Template{}, err
}
if err := attachActivityTemplateChildren(ctx, q, &item); err != nil {
return domain.Template{}, err
}
return item, nil
}
func getActivityTemplateBase(ctx context.Context, q activityTemplateQuerier, appCode string, templateID string, forUpdate bool) (domain.Template, error) {
query := activityTemplateSelect + ` WHERE app_code = ? AND template_id = ?`
if forUpdate {
query += ` FOR UPDATE`
}
item, err := scanActivityTemplate(q.QueryRowContext(ctx, query, appCode, strings.TrimSpace(templateID)))
if errors.Is(err, sql.ErrNoRows) {
return domain.Template{}, xerr.New(xerr.NotFound, "activity template not found")
}
return item, err
}
func scanActivityTemplate(scanner activityTemplateScanner) (domain.Template, error) {
var item domain.Template
err := scanner.Scan(
&item.AppCode, &item.TemplateID, &item.TemplateCode, &item.Name, &item.ActivityType, &item.Status,
&item.StartMS, &item.EndMS, &item.AllRegions, &item.DailyLeaderboardSize, &item.TotalLeaderboardSize,
&item.DisplayConfigJSON, &item.Revision, &item.PublishedVersion,
&item.CreatedByAdminID, &item.UpdatedByAdminID, &item.PublishedByAdminID,
&item.CreatedAtMS, &item.UpdatedAtMS, &item.PublishedAtMS, &item.ArchivedAtMS,
)
return item, err
}
func attachActivityTemplateChildren(ctx context.Context, q activityTemplateQuerier, item *domain.Template) error {
item.RegionIDs = []int64{}
item.Locales = []domain.Locale{}
item.Gifts = []domain.Gift{}
item.Tasks = []domain.Task{}
item.RankRewards = []domain.RankReward{}
item.Assets = []domain.Asset{}
rows, err := q.QueryContext(ctx, `SELECT region_id FROM activity_template_regions WHERE app_code = ? AND template_id = ? ORDER BY region_id`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var regionID int64
if err := rows.Scan(&regionID); err != nil {
rows.Close()
return err
}
item.RegionIDs = append(item.RegionIDs, regionID)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
rows, err = q.QueryContext(ctx, `SELECT locale, title, rules FROM activity_template_locales WHERE app_code = ? AND template_id = ? ORDER BY locale`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var locale domain.Locale
if err := rows.Scan(&locale.Locale, &locale.Title, &locale.Rules); err != nil {
rows.Close()
return err
}
item.Locales = append(item.Locales, locale)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
rows, err = q.QueryContext(ctx, `
SELECT gift_id, sort_order, name, icon_url, coin_price, price_version
FROM activity_template_gifts WHERE app_code = ? AND template_id = ? ORDER BY sort_order, gift_id`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var gift domain.Gift
if err := rows.Scan(&gift.GiftID, &gift.SortOrder, &gift.Name, &gift.IconURL, &gift.CoinPrice, &gift.PriceVersion); err != nil {
rows.Close()
return err
}
item.Gifts = append(item.Gifts, gift)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
rows, err = q.QueryContext(ctx, `
SELECT task_key, task_type, target_value, reward_resource_group_id, sort_order
FROM activity_template_tasks WHERE app_code = ? AND template_id = ? ORDER BY sort_order, task_key`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var task domain.Task
if err := rows.Scan(&task.TaskKey, &task.TaskType, &task.TargetValue, &task.RewardResourceGroupID, &task.SortOrder); err != nil {
rows.Close()
return err
}
item.Tasks = append(item.Tasks, task)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
rows, err = q.QueryContext(ctx, `
SELECT board_type, rank_from, rank_to, resource_group_id, sort_order
FROM activity_template_rank_rewards WHERE app_code = ? AND template_id = ? ORDER BY board_type, rank_from, rank_to`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var reward domain.RankReward
if err := rows.Scan(&reward.BoardType, &reward.RankFrom, &reward.RankTo, &reward.ResourceGroupID, &reward.SortOrder); err != nil {
rows.Close()
return err
}
item.RankRewards = append(item.RankRewards, reward)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
rows, err = q.QueryContext(ctx, `
SELECT asset_key, locale, url, media_type, sort_order
FROM activity_template_assets WHERE app_code = ? AND template_id = ? ORDER BY asset_key, locale, sort_order`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
for rows.Next() {
var asset domain.Asset
if err := rows.Scan(&asset.AssetKey, &asset.Locale, &asset.URL, &asset.MediaType, &asset.SortOrder); err != nil {
rows.Close()
return err
}
item.Assets = append(item.Assets, asset)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
return nil
}
func replaceActivityTemplateChildren(ctx context.Context, tx *sql.Tx, item domain.Template, nowMS int64) error {
for _, table := range []string{
"activity_template_regions",
"activity_template_locales",
"activity_template_gifts",
"activity_template_tasks",
"activity_template_rank_rewards",
"activity_template_assets",
} {
if _, err := tx.ExecContext(ctx, `DELETE FROM `+table+` WHERE app_code = ? AND template_id = ?`, item.AppCode, item.TemplateID); err != nil {
return err
}
}
for _, regionID := range item.RegionIDs {
if _, err := tx.ExecContext(ctx, `INSERT INTO activity_template_regions (app_code, template_id, region_id, created_at_ms) VALUES (?, ?, ?, ?)`, item.AppCode, item.TemplateID, regionID, nowMS); err != nil {
return err
}
}
for _, locale := range item.Locales {
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_locales (app_code, template_id, locale, title, rules, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?)`, item.AppCode, item.TemplateID, locale.Locale, locale.Title, locale.Rules, nowMS, nowMS); err != nil {
return err
}
}
for _, gift := range item.Gifts {
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_gifts (app_code, template_id, gift_id, sort_order, name, icon_url, coin_price, price_version, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.AppCode, item.TemplateID, gift.GiftID, gift.SortOrder, gift.Name, gift.IconURL, gift.CoinPrice, gift.PriceVersion, nowMS, nowMS); err != nil {
return err
}
}
for _, task := range item.Tasks {
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_tasks (app_code, template_id, task_key, task_type, target_value, reward_resource_group_id, sort_order, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.AppCode, item.TemplateID, task.TaskKey, task.TaskType, task.TargetValue, task.RewardResourceGroupID, task.SortOrder, nowMS, nowMS); err != nil {
return err
}
}
for _, reward := range item.RankRewards {
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_rank_rewards (app_code, template_id, board_type, rank_from, rank_to, resource_group_id, sort_order, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.AppCode, item.TemplateID, reward.BoardType, reward.RankFrom, reward.RankTo, reward.ResourceGroupID, reward.SortOrder, nowMS, nowMS); err != nil {
return err
}
}
for _, asset := range item.Assets {
if _, err := tx.ExecContext(ctx, `
INSERT INTO activity_template_assets (app_code, template_id, asset_key, locale, url, media_type, sort_order, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
item.AppCode, item.TemplateID, asset.AssetKey, asset.Locale, asset.URL, asset.MediaType, asset.SortOrder, nowMS, nowMS); err != nil {
return err
}
}
return nil
}
func ensureNoPublishedActivityTemplateOverlap(ctx context.Context, tx *sql.Tx, item domain.Template) error {
rows, err := tx.QueryContext(ctx, activityTemplateSelect+`
WHERE app_code = ? AND template_id <> ? AND status = ? AND start_ms < ? AND end_ms > ?
ORDER BY start_ms, template_id
FOR UPDATE`, item.AppCode, item.TemplateID, domain.StatusPublished, item.EndMS, item.StartMS)
if err != nil {
return err
}
candidates := make([]domain.Template, 0)
for rows.Next() {
candidate, err := scanActivityTemplate(rows)
if err != nil {
rows.Close()
return err
}
candidates = append(candidates, candidate)
}
if err := rows.Err(); err != nil {
rows.Close()
return err
}
if err := rows.Close(); err != nil {
return err
}
for index := range candidates {
if err := attachActivityTemplateRegions(ctx, tx, &candidates[index]); err != nil {
return err
}
if activityTemplateScopesOverlap(item, candidates[index]) {
return xerr.New(xerr.Conflict, "published activity template overlaps another activity in the same region scope")
}
}
return nil
}
func activityTemplateScopesOverlap(left domain.Template, right domain.Template) bool {
if left.AllRegions || right.AllRegions {
return true
}
seen := make(map[int64]struct{}, len(left.RegionIDs))
for _, regionID := range left.RegionIDs {
seen[regionID] = struct{}{}
}
for _, regionID := range right.RegionIDs {
if _, exists := seen[regionID]; exists {
return true
}
}
return false
}
func attachActivityTemplateRegions(ctx context.Context, q activityTemplateQuerier, item *domain.Template) error {
rows, err := q.QueryContext(ctx, `SELECT region_id FROM activity_template_regions WHERE app_code = ? AND template_id = ? ORDER BY region_id`, item.AppCode, item.TemplateID)
if err != nil {
return err
}
defer rows.Close()
item.RegionIDs = []int64{}
for rows.Next() {
var regionID int64
if err := rows.Scan(&regionID); err != nil {
return err
}
item.RegionIDs = append(item.RegionIDs, regionID)
}
return rows.Err()
}
func attachActivityTemplateSummaryRegions(ctx context.Context, q activityTemplateQuerier, items []domain.Summary) error {
if len(items) == 0 {
return nil
}
placeholders := make([]string, 0, len(items))
args := make([]any, 0, len(items)+1)
args = append(args, items[0].AppCode)
indexes := make(map[string]int, len(items))
for index := range items {
items[index].RegionIDs = []int64{}
placeholders = append(placeholders, "?")
args = append(args, items[index].TemplateID)
indexes[items[index].TemplateID] = index
}
rows, err := q.QueryContext(ctx, `
SELECT template_id, region_id
FROM activity_template_regions
WHERE app_code = ? AND template_id IN (`+strings.Join(placeholders, ",")+`)
ORDER BY template_id, region_id`, args...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var templateID string
var regionID int64
if err := rows.Scan(&templateID, &regionID); err != nil {
return err
}
if index, exists := indexes[templateID]; exists {
items[index].RegionIDs = append(items[index].RegionIDs, regionID)
}
}
return rows.Err()
}
func activityTemplateWriteError(err error) error {
if err == nil {
return nil
}
var mysqlErr *mysqlDriver.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
return xerr.New(xerr.Conflict, "activity template code or child key already exists")
}
return err
}

View File

@ -0,0 +1,480 @@
package mysql
import (
"context"
"fmt"
"strings"
"testing"
"time"
"hyapp/internal/testutil/mysqlschema"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
func TestActivityTemplateListKeywordSearchesIDCodeAndName(t *testing.T) {
ctx := appcode.WithContext(context.Background(), "hyapp")
where, args := activityTemplateListWhere(ctx, domain.ListQuery{Keyword: "acttpl_123"})
for _, field := range []string{"t.template_id LIKE ?", "t.template_code LIKE ?", "t.name LIKE ?"} {
if !strings.Contains(where, field) {
t.Fatalf("keyword where clause missing %s: %s", field, where)
}
}
if len(args) != 5 {
t.Fatalf("keyword args = %v, want app_code, default archived exclusion and three LIKE args", args)
}
for index := 2; index < len(args); index++ {
if args[index] != "%acttpl_123%" {
t.Fatalf("keyword arg %d = %v", index, args[index])
}
}
}
func TestPrepareActivityTemplateRankSettlementSeparatesMaterializationFromExpiredJobRecovery(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_reward_recovery_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
ctx := appcode.WithContext(context.Background(), "hyapp")
item := createRepositoryTemplate(t, ctx, repository, "reward_crash_recovery", false, []int64{1}, 1_000, 2_000)
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished, ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}, 100)
if err != nil {
t.Fatalf("publish template: %v", err)
}
period := domain.RankPeriod{
AppCode: "hyapp", TemplateID: item.TemplateID, TemplateCode: item.TemplateCode, VersionNo: item.PublishedVersion,
BoardType: domain.BoardTypeDaily, PeriodKey: time.UnixMilli(1_000).UTC().Format("2006-01-02"),
}
if _, err := repository.db.ExecContext(ctx, `
INSERT INTO activity_template_rank_snapshots (
app_code, template_id, template_code, version_no, board_type, period_key, rank_no,
user_id, score, gift_count, coin_amount, first_scored_at_ms, snapshot_at_ms
) VALUES (?, ?, ?, ?, ?, ?, 1, 70001, 500, 5, 500, 1100, 2100)`,
period.AppCode, period.TemplateID, period.TemplateCode, period.VersionNo, period.BoardType, period.PeriodKey); err != nil {
t.Fatalf("insert frozen rank: %v", err)
}
if _, err := repository.db.ExecContext(ctx, `
INSERT INTO activity_template_reward_jobs (
app_code, reward_job_id, template_id, template_code, version_no, board_type, period_key,
rank_no, user_id, score, resource_group_id, wallet_command_id, status, attempt_count,
created_at_ms, updated_at_ms
) VALUES (?, 'rewardjob_crashed', ?, ?, ?, ?, ?, 1, 70001, 500, 99,
'activity-template-rank:crashed', ?, 1, 2100, 2100)`,
period.AppCode, period.TemplateID, period.TemplateCode, period.VersionNo, period.BoardType, period.PeriodKey,
domain.ClaimStatusRunning); err != nil {
t.Fatalf("insert crashed running job: %v", err)
}
periods, err := repository.ClaimDueActivityTemplateRankPeriods(ctx, "materializer", 200_000, time.Minute, 10)
if err != nil {
t.Fatalf("ClaimDueActivityTemplateRankPeriods() error = %v", err)
}
claimed := false
for _, candidate := range periods {
if candidate.TemplateID == period.TemplateID && candidate.BoardType == period.BoardType && candidate.PeriodKey == period.PeriodKey {
claimed = true
break
}
}
if !claimed {
t.Fatalf("daily period was not claimed for materialization: %+v", periods)
}
jobs, err := repository.PrepareActivityTemplateRankSettlement(ctx, period, 200_001)
if err != nil {
t.Fatalf("PrepareActivityTemplateRankSettlement() error = %v", err)
}
if len(jobs) != 0 {
t.Fatalf("PrepareActivityTemplateRankSettlement() jobs = %+v, want materialization only", jobs)
}
var periodStatus string
if err := repository.db.QueryRowContext(ctx, `
SELECT status FROM activity_template_rank_periods
WHERE app_code = ? AND template_id = ? AND version_no = ? AND board_type = ? AND period_key = ?`,
period.AppCode, period.TemplateID, period.VersionNo, period.BoardType, period.PeriodKey).Scan(&periodStatus); err != nil {
t.Fatalf("read materialized period status: %v", err)
}
if periodStatus != domain.PeriodStatusRewarding {
t.Fatalf("materialized period status = %q, want rewarding", periodStatus)
}
jobs, err = repository.ClaimActivityTemplateRewardJobs(ctx, "recovery-worker", 200_002, time.Minute, 10)
if err != nil {
t.Fatalf("ClaimActivityTemplateRewardJobs() error = %v", err)
}
if len(jobs) != 1 || jobs[0].RewardJobID != "rewardjob_crashed" || jobs[0].Status != domain.ClaimStatusRunning || jobs[0].AttemptCount != 2 {
t.Fatalf("expired job recovery = %+v", jobs)
}
}
func TestActivityTemplateSettlementDiscoverySkipsFiftyBlockedPeriodsBeforePending(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_period_starvation_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
ctx := appcode.WithContext(context.Background(), "hyapp")
const nowMS = int64(100_000)
for index := 0; index < 50; index++ {
templateID := "blocked-" + fmt.Sprintf("%02d", index)
if _, err := repository.db.ExecContext(ctx, `
INSERT INTO activity_template_rank_periods (
app_code, template_id, template_code, version_no, board_type, period_key,
period_start_ms, period_end_ms, settle_after_ms, leaderboard_size, status,
locked_by, lock_until_ms, settled_at_ms, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, 1, 'total', 'total', 1, 2, ?, 100, ?, '', 0, 0, 1, 1)`,
"hyapp", templateID, templateID, int64(index+1), domain.PeriodStatusSettling); err != nil {
t.Fatalf("insert blocked period %d: %v", index, err)
}
status := domain.ClaimStatusFailed
nextRetryAtMS := nowMS + int64(time.Hour/time.Millisecond)
if index%2 == 0 {
status = domain.ClaimStatusDead
nextRetryAtMS = 0
}
if _, err := repository.db.ExecContext(ctx, `
INSERT INTO activity_template_reward_jobs (
app_code, reward_job_id, template_id, template_code, version_no, board_type, period_key,
rank_no, user_id, score, resource_group_id, wallet_command_id, status, attempt_count,
max_attempts, next_retry_at_ms, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, 1, 'total', 'total', 1, ?, 1, 1, ?, ?, 1, 8, ?, 1, 1)`,
"hyapp", "job-"+templateID, templateID, templateID, int64(index+1), "wallet-"+templateID,
status, nextRetryAtMS); err != nil {
t.Fatalf("insert blocked job %d: %v", index, err)
}
}
if _, err := repository.db.ExecContext(ctx, `
INSERT INTO activity_template_rank_periods (
app_code, template_id, template_code, version_no, board_type, period_key,
period_start_ms, period_end_ms, settle_after_ms, leaderboard_size, status,
locked_by, lock_until_ms, settled_at_ms, created_at_ms, updated_at_ms
) VALUES ('hyapp', 'pending-after-blocked', 'pending-after-blocked', 1, 'total', 'total',
1, 2, 1000, 100, ?, '', 0, 0, 1, 1)`, domain.PeriodStatusPending); err != nil {
t.Fatalf("insert pending period: %v", err)
}
periods, err := repository.ClaimDueActivityTemplateRankPeriods(ctx, "settler", nowMS, time.Minute, 50)
if err != nil {
t.Fatalf("ClaimDueActivityTemplateRankPeriods() error = %v", err)
}
if len(periods) != 1 || periods[0].TemplateID != "pending-after-blocked" {
t.Fatalf("discovered periods = %+v, want only pending-after-blocked", periods)
}
if len(periods) == 50 {
t.Fatal("blocked settling periods must not keep settlement has_more true")
}
}
func TestActivityTemplateListTimeFilterExcludesUnscheduledDrafts(t *testing.T) {
ctx := appcode.WithContext(context.Background(), "hyapp")
where, _ := activityTemplateListWhere(ctx, domain.ListQuery{EndMS: 2_000})
if !strings.Contains(where, "t.start_ms > 0 AND t.end_ms > t.start_ms") || !strings.Contains(where, "t.start_ms < ?") {
t.Fatalf("end-only time filter does not require a valid scheduled period: %s", where)
}
}
func TestActivityTemplateScopesOverlap(t *testing.T) {
tests := []struct {
name string
left domain.Template
right domain.Template
want bool
}{
{name: "left global", left: domain.Template{AllRegions: true}, right: domain.Template{RegionIDs: []int64{1}}, want: true},
{name: "right global", left: domain.Template{RegionIDs: []int64{1}}, right: domain.Template{AllRegions: true}, want: true},
{name: "shared region", left: domain.Template{RegionIDs: []int64{1, 2}}, right: domain.Template{RegionIDs: []int64{2, 3}}, want: true},
{name: "disjoint regions", left: domain.Template{RegionIDs: []int64{1}}, right: domain.Template{RegionIDs: []int64{2}}, want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := activityTemplateScopesOverlap(tt.left, tt.right); got != tt.want {
t.Fatalf("activityTemplateScopesOverlap() = %v, want %v", got, tt.want)
}
})
}
}
func TestActivityTemplateRankPeriodWatermarkIsExclusive(t *testing.T) {
const settleAfterMS = int64(122_000)
if accepted, reason := activityTemplateRankPeriodAcceptsEvent(domain.PeriodStatusPending, settleAfterMS, settleAfterMS-1); !accepted || reason != "" {
t.Fatalf("event before watermark = (%v, %q), want accepted", accepted, reason)
}
if accepted, reason := activityTemplateRankPeriodAcceptsEvent(domain.PeriodStatusPending, settleAfterMS, settleAfterMS); accepted || reason != "settlement_window_closed" {
t.Fatalf("event at watermark = (%v, %q), want settlement_window_closed", accepted, reason)
}
if accepted, reason := activityTemplateRankPeriodAcceptsEvent(domain.PeriodStatusSettling, settleAfterMS, settleAfterMS-1); accepted || reason != "rank_period_closed" {
t.Fatalf("event for settling period = (%v, %q), want rank_period_closed", accepted, reason)
}
if accepted, reason := activityTemplateRankPeriodAcceptsEvent(domain.PeriodStatusRewarding, settleAfterMS, settleAfterMS-1); accepted || reason != "rank_period_closed" {
t.Fatalf("event for rewarding period = (%v, %q), want rank_period_closed", accepted, reason)
}
}
func TestActivityTemplateLateGiftIsAcceptedBeforeWatermarkAndRejectedAtSettlement(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_late_gift_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
repository.SetActivityTemplateSettlementLateness(2 * time.Minute)
ctx := appcode.WithContext(context.Background(), "hyapp")
item := createRepositoryTemplate(t, ctx, repository, "late_gift_watermark", false, []int64{1}, 1_000, 2_000)
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished, ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}, 100)
if err != nil {
t.Fatalf("publish template: %v", err)
}
periodKey := time.UnixMilli(1_999).UTC().Format("2006-01-02")
var settleAfterMS int64
if err := repository.db.QueryRowContext(ctx, `
SELECT settle_after_ms FROM activity_template_rank_periods
WHERE app_code = ? AND template_id = ? AND version_no = ? AND board_type = ? AND period_key = ?`,
"hyapp", item.TemplateID, item.PublishedVersion, domain.BoardTypeDaily, periodKey).Scan(&settleAfterMS); err != nil {
t.Fatalf("read settlement watermark: %v", err)
}
if settleAfterMS != 2_000+(2*time.Minute).Milliseconds() {
t.Fatalf("settle_after_ms = %d", settleAfterMS)
}
periods, err := repository.ClaimDueActivityTemplateRankPeriods(ctx, "worker-before", settleAfterMS-1, time.Minute, 10)
if err != nil || len(periods) != 0 {
t.Fatalf("claim before watermark = %+v, err=%v", periods, err)
}
accepted, err := repository.ConsumeActivityTemplateGiftEvent(ctx, domain.GiftEvent{
EventID: "event-before-watermark", UserID: 70001, GiftID: "gift_rose", GiftCount: 1,
CoinAmount: 100, RegionID: 1, OccurredAtMS: 1_999,
}, settleAfterMS-1)
if err != nil || accepted.Status != domain.EventStatusConsumed {
t.Fatalf("consume before watermark = %+v, err=%v", accepted, err)
}
periods, err = repository.ClaimDueActivityTemplateRankPeriods(ctx, "worker-at", settleAfterMS, time.Minute, 10)
if err != nil || len(periods) != 2 {
t.Fatalf("claim at watermark = %+v, err=%v", periods, err)
}
late, err := repository.ConsumeActivityTemplateGiftEvent(ctx, domain.GiftEvent{
EventID: "event-at-watermark", UserID: 70001, GiftID: "gift_rose", GiftCount: 1,
CoinAmount: 100, RegionID: 1, OccurredAtMS: 1_999,
}, settleAfterMS)
if err != nil || late.Status != domain.EventStatusSkipped || late.SkipReason != "daily:settlement_window_closed;total:settlement_window_closed" {
t.Fatalf("consume at watermark = %+v, err=%v", late, err)
}
var score int64
if err := repository.db.QueryRowContext(ctx, `
SELECT score FROM activity_template_user_total_scores
WHERE app_code = ? AND template_id = ? AND version_no = ? AND user_id = ?`,
"hyapp", item.TemplateID, item.PublishedVersion, 70001).Scan(&score); err != nil {
t.Fatalf("read total score: %v", err)
}
if score != 100 {
t.Fatalf("total score = %d, want only the accepted event", score)
}
}
func TestActivityTemplateLateGiftStillUpdatesOpenTotalAfterDailySettlement(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_split_watermark_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
repository.SetActivityTemplateSettlementLateness(2 * time.Minute)
ctx := appcode.WithContext(context.Background(), "hyapp")
dayStart := time.Date(2026, 7, 14, 0, 0, 0, 0, time.UTC).UnixMilli()
dayEnd := dayStart + int64(24*time.Hour/time.Millisecond)
item := createRepositoryTemplate(t, ctx, repository, "split_daily_total_watermark", false, []int64{1}, dayStart, dayEnd+int64(24*time.Hour/time.Millisecond))
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished, ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}, dayStart-1_000)
if err != nil {
t.Fatalf("publish template: %v", err)
}
processingMS := dayEnd + (2 * time.Minute).Milliseconds()
periods, err := repository.ClaimDueActivityTemplateRankPeriods(ctx, "daily-settler", processingMS, time.Minute, 10)
if err != nil || len(periods) != 1 || periods[0].BoardType != domain.BoardTypeDaily {
t.Fatalf("claim first daily period = %+v, err=%v", periods, err)
}
result, err := repository.ConsumeActivityTemplateGiftEvent(ctx, domain.GiftEvent{
EventID: "late-daily-open-total", UserID: 70002, GiftID: "gift_rose", GiftCount: 2,
CoinAmount: 250, RegionID: 1, OccurredAtMS: dayEnd - 1,
}, processingMS)
if err != nil || result.Status != domain.EventStatusConsumed || result.SkipReason != "daily:settlement_window_closed" {
t.Fatalf("consume split-watermark gift = %+v, err=%v", result, err)
}
var totalScore int64
if err := repository.db.QueryRowContext(ctx, `
SELECT score FROM activity_template_user_total_scores
WHERE app_code = ? AND template_id = ? AND version_no = ? AND user_id = ?`,
"hyapp", item.TemplateID, item.PublishedVersion, 70002).Scan(&totalScore); err != nil {
t.Fatalf("read total score: %v", err)
}
if totalScore != 250 {
t.Fatalf("total score = %d, want 250", totalScore)
}
var dailyRows int
if err := repository.db.QueryRowContext(ctx, `
SELECT COUNT(*) FROM activity_template_user_day_scores
WHERE app_code = ? AND template_id = ? AND version_no = ? AND user_id = ?`,
"hyapp", item.TemplateID, item.PublishedVersion, 70002).Scan(&dailyRows); err != nil || dailyRows != 0 {
t.Fatalf("closed daily score rows = %d, err=%v", dailyRows, err)
}
}
func TestActivityTemplateRepositoryPublishSerializesScopeOverlapAndStoresVersion(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_template_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
ctx := appcode.WithContext(context.Background(), "hyapp")
first := createRepositoryTemplate(t, ctx, repository, "scope_first", false, []int64{1}, 1_000, 2_000)
first, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: first.TemplateID, Status: domain.StatusPublished, ExpectedRevision: first.Revision, OperatorAdminID: 90001,
}, 100)
if err != nil {
t.Fatalf("publish first template: %v", err)
}
if first.PublishedVersion != 1 || first.Revision != 2 {
t.Fatalf("first published metadata = %+v", first)
}
version, err := repository.GetActivityTemplateVersion(ctx, first.TemplateID, 1)
if err != nil {
t.Fatalf("get first version: %v", err)
}
if version.Snapshot.Status != domain.StatusPublished || version.Snapshot.TemplateCode != first.TemplateCode || version.Snapshot.Revision != first.Revision {
t.Fatalf("published snapshot = %+v", version.Snapshot)
}
overlap := createRepositoryTemplate(t, ctx, repository, "scope_overlap", false, []int64{1}, 1_500, 2_500)
_, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: overlap.TemplateID, Status: domain.StatusPublished, ExpectedRevision: overlap.Revision, OperatorAdminID: 90001,
}, 110)
if !xerr.IsCode(err, xerr.Conflict) {
t.Fatalf("overlapping same-region publish error = %v, want conflict", err)
}
disjoint := createRepositoryTemplate(t, ctx, repository, "scope_disjoint", false, []int64{2}, 1_500, 2_500)
if _, err := repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: disjoint.TemplateID, Status: domain.StatusPublished, ExpectedRevision: disjoint.Revision, OperatorAdminID: 90001,
}, 120); err != nil {
t.Fatalf("publish disjoint-region template: %v", err)
}
global := createRepositoryTemplate(t, ctx, repository, "scope_global", true, nil, 1_700, 1_800)
_, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: global.TemplateID, Status: domain.StatusPublished, ExpectedRevision: global.Revision, OperatorAdminID: 90001,
}, 130)
if !xerr.IsCode(err, xerr.Conflict) {
t.Fatalf("global overlapping publish error = %v, want conflict", err)
}
// [start_ms,end_ms) 允许一个活动在另一个活动结束的同一毫秒开始。
adjacent := createRepositoryTemplate(t, ctx, repository, "scope_adjacent", false, []int64{1}, 2_000, 3_000)
if _, err := repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: adjacent.TemplateID, Status: domain.StatusPublished, ExpectedRevision: adjacent.Revision, OperatorAdminID: 90001,
}, 140); err != nil {
t.Fatalf("publish adjacent template: %v", err)
}
}
func TestActivityTemplateVersionQueryJoinsDisableAndRepublishRuntimeWindows(t *testing.T) {
schema := mysqlschema.New(t, mysqlschema.Config{
EnvVar: "ACTIVITY_SERVICE_MYSQL_TEST_DSN",
InitDBPath: mysqlschema.InitDBPath(t, mysqlschema.CallerFile(t, 1), "..", "..", "..", "deploy", "mysql", "initdb", "001_activity_service.sql"),
DatabasePrefix: "hyapp_activity_version_runtime_test",
})
repository, err := Open(context.Background(), schema.DSN)
if err != nil {
t.Fatalf("open repository: %v", err)
}
t.Cleanup(func() { _ = repository.Close() })
ctx := appcode.WithContext(context.Background(), "hyapp")
item := createRepositoryTemplate(t, ctx, repository, "version_runtime_window", false, []int64{1}, 100, 2_000)
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished, ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}, 100)
if err != nil {
t.Fatalf("publish v1: %v", err)
}
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusDisabled, ExpectedRevision: item.Revision, OperatorAdminID: 90001,
}, 500)
if err != nil {
t.Fatalf("disable v1: %v", err)
}
item, err = repository.SetActivityTemplateStatus(ctx, domain.StatusCommand{
TemplateID: item.TemplateID, Status: domain.StatusPublished, ExpectedRevision: item.Revision, OperatorAdminID: 90002,
}, 600)
if err != nil {
t.Fatalf("republish v2: %v", err)
}
versions, total, err := repository.ListActivityTemplateVersions(ctx, item.TemplateID, 1, 20)
if err != nil {
t.Fatalf("list versions: %v", err)
}
if total != 2 || len(versions) != 2 {
t.Fatalf("version total/items = %d/%d", total, len(versions))
}
if versions[0].VersionNo != 2 || versions[0].RuntimeFromMS != 600 || versions[0].RuntimeToMS != 0 {
t.Fatalf("v2 runtime window = %+v", versions[0])
}
if versions[1].VersionNo != 1 || versions[1].RuntimeFromMS != 100 || versions[1].RuntimeToMS != 500 {
t.Fatalf("v1 runtime window = %+v", versions[1])
}
v1, err := repository.GetActivityTemplateVersion(ctx, item.TemplateID, 1)
if err != nil {
t.Fatalf("get v1: %v", err)
}
if v1.RuntimeFromMS != 100 || v1.RuntimeToMS != 500 {
t.Fatalf("get v1 runtime window = %+v", v1)
}
}
func createRepositoryTemplate(t *testing.T, ctx context.Context, repository *Repository, code string, allRegions bool, regionIDs []int64, startMS int64, endMS int64) domain.Template {
t.Helper()
item, err := repository.CreateActivityTemplate(ctx, domain.Template{
TemplateCode: code, Name: code, ActivityType: domain.ActivityTypeGiftChallenge,
StartMS: startMS, EndMS: endMS, AllRegions: allRegions, RegionIDs: regionIDs,
Gifts: []domain.Gift{{
GiftID: "gift_rose", SortOrder: 1, Name: "Rose", IconURL: "https://cdn.example.com/gift_rose.png",
CoinPrice: 100, PriceVersion: "v1",
}},
DailyLeaderboardSize: 100, TotalLeaderboardSize: 100, DisplayConfigJSON: `{}`,
}, 90001, 10)
if err != nil {
t.Fatalf("create template %s: %v", code, err)
}
return item
}

View File

@ -0,0 +1,329 @@
package mysql
import "context"
// ensureActivityTemplateRuntimeTables mirrors migration 011 for explicitly enabled local/test auto-migrate.
func (r *Repository) ensureActivityTemplateRuntimeTables(ctx context.Context) error {
statements := []string{
`CREATE TABLE IF NOT EXISTS activity_template_runtime_versions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
start_ms BIGINT NOT NULL,
end_ms BIGINT NOT NULL,
all_regions BOOLEAN NOT NULL DEFAULT FALSE,
runtime_from_ms BIGINT NOT NULL,
runtime_to_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_runtime_current (app_code, template_code, start_ms, end_ms, runtime_from_ms, runtime_to_ms),
KEY idx_activity_template_runtime_due (app_code, end_ms, runtime_to_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变运行版本启停窗口'`,
`CREATE TABLE IF NOT EXISTS activity_template_runtime_regions (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
version_no BIGINT NOT NULL,
region_id BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, region_id),
KEY idx_activity_template_runtime_region (app_code, region_id, template_id, version_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行版本区域快照'`,
`CREATE TABLE IF NOT EXISTS activity_template_event_consumption (
app_code VARCHAR(32) NOT NULL,
source_event_id VARCHAR(128) NOT NULL,
event_type VARCHAR(64) NOT NULL,
template_id VARCHAR(96) NOT NULL DEFAULT '',
template_code VARCHAR(64) NOT NULL DEFAULT '',
version_no BIGINT NOT NULL DEFAULT 0,
user_id BIGINT NOT NULL DEFAULT 0,
region_id BIGINT NOT NULL DEFAULT 0,
task_day VARCHAR(10) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
skip_reason VARCHAR(128) NOT NULL DEFAULT '',
occurred_at_ms BIGINT NOT NULL,
consumed_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, source_event_id),
KEY idx_activity_template_event_template (app_code, template_id, version_no, occurred_at_ms),
KEY idx_activity_template_event_status (app_code, status, consumed_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版运行事件幂等事实'`,
`CREATE TABLE IF NOT EXISTS activity_template_user_day_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
day_start_ms BIGINT NOT NULL,
day_end_ms BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
KEY idx_activity_template_day_rank (app_code, template_id, version_no, task_day, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 UTC 日榜实时分数'`,
`CREATE TABLE IF NOT EXISTS activity_template_user_total_scores (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL DEFAULT 0,
gift_count BIGINT NOT NULL DEFAULT 0,
coin_amount BIGINT NOT NULL DEFAULT 0,
first_scored_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, user_id),
KEY idx_activity_template_total_rank (app_code, template_id, version_no, score DESC, first_scored_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版活动总榜实时分数'`,
`CREATE TABLE IF NOT EXISTS activity_template_daily_visits (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
region_id BIGINT NOT NULL DEFAULT 0,
command_id VARCHAR(128) NOT NULL,
visited_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id),
UNIQUE KEY uk_activity_template_visit_command (app_code, command_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版 daily_visit 自然幂等事实'`,
`CREATE TABLE IF NOT EXISTS activity_template_task_progress (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
user_id BIGINT NOT NULL,
first_region_id BIGINT NOT NULL DEFAULT 0,
task_key VARCHAR(64) NOT NULL,
task_type VARCHAR(32) NOT NULL,
target_value BIGINT NOT NULL,
progress_value BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
sort_order INT NOT NULL DEFAULT 0,
status VARCHAR(24) NOT NULL,
completed_at_ms BIGINT NOT NULL DEFAULT 0,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
claimed_at_ms BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, task_day, user_id, task_key),
KEY idx_activity_template_task_data (app_code, template_id, version_no, task_day, task_key, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版用户每日任务进度'`,
`CREATE TABLE IF NOT EXISTS activity_template_task_claims (
app_code VARCHAR(32) NOT NULL,
claim_id VARCHAR(96) NOT NULL,
command_id VARCHAR(128) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
task_day VARCHAR(10) NOT NULL,
task_key VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
earning_region_id BIGINT NOT NULL DEFAULT 0,
attribution_at_ms BIGINT NOT NULL DEFAULT 0,
reward_resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(160) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, claim_id),
UNIQUE KEY uk_activity_template_claim_command (app_code, command_id),
UNIQUE KEY uk_activity_template_claim_task (app_code, template_id, version_no, task_day, user_id, task_key),
UNIQUE KEY uk_activity_template_claim_wallet (app_code, wallet_command_id),
KEY idx_activity_template_claim_status (app_code, status, updated_at_ms),
KEY idx_activity_template_claim_admin (app_code, template_id, version_no, task_day, task_key, user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版任务资源组领奖事实'`,
`CREATE TABLE IF NOT EXISTS activity_template_rank_periods (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
period_start_ms BIGINT NOT NULL,
period_end_ms BIGINT NOT NULL,
settle_after_ms BIGINT NOT NULL COMMENT '结算水位周期结束加允许迟到窗口UTC epoch ms',
leaderboard_size INT NOT NULL,
status VARCHAR(24) NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
settled_at_ms BIGINT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key),
KEY idx_activity_template_period_settle_due (app_code, status, settle_after_ms, lock_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单结算周期'`,
`CREATE TABLE IF NOT EXISTS activity_template_rank_snapshots (
app_code VARCHAR(32) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
gift_count BIGINT NOT NULL,
coin_amount BIGINT NOT NULL,
first_scored_at_ms BIGINT NOT NULL,
snapshot_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_rank_user (app_code, template_id, version_no, board_type, period_key, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版冻结榜单快照'`,
`CREATE TABLE IF NOT EXISTS activity_template_reward_jobs (
app_code VARCHAR(32) NOT NULL,
reward_job_id VARCHAR(96) NOT NULL,
template_id VARCHAR(96) NOT NULL,
template_code VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL,
board_type VARCHAR(16) NOT NULL,
period_key VARCHAR(24) NOT NULL,
rank_no INT NOT NULL,
user_id BIGINT NOT NULL,
score BIGINT NOT NULL,
resource_group_id BIGINT NOT NULL,
reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '',
reward_snapshot_json JSON NULL,
wallet_command_id VARCHAR(192) NOT NULL,
wallet_grant_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(24) NOT NULL,
failure_reason VARCHAR(512) NOT NULL DEFAULT '',
attempt_count INT NOT NULL DEFAULT 0,
max_attempts INT NOT NULL DEFAULT 8 COMMENT '自动投递总尝试上限dead 手工复活时只扩一轮预算',
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次可领取时间UTC epoch ms',
locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前 job lease owner',
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'job lease 过期时间UTC epoch ms',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
granted_at_ms BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (app_code, reward_job_id),
UNIQUE KEY uk_activity_template_reward_rank (app_code, template_id, version_no, board_type, period_key, rank_no),
UNIQUE KEY uk_activity_template_reward_wallet (app_code, wallet_command_id),
KEY idx_activity_template_reward_query (app_code, template_id, version_no, board_type, period_key, status),
KEY idx_activity_template_reward_retry (app_code, status, updated_at_ms),
KEY idx_activity_template_reward_claim (app_code, status, next_retry_at_ms, lock_until_ms, created_at_ms, reward_job_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版榜单奖励发放任务'`,
`CREATE TABLE IF NOT EXISTS activity_template_stats_outbox (
outbox_id VARCHAR(96) NOT NULL,
app_code VARCHAR(32) NOT NULL,
event_type VARCHAR(64) NOT NULL,
payload BLOB NOT NULL,
status VARCHAR(24) NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
next_retry_at_ms BIGINT NOT NULL,
locked_by VARCHAR(128) NOT NULL DEFAULT '',
lock_until_ms BIGINT NOT NULL DEFAULT 0,
last_error VARCHAR(512) NOT NULL DEFAULT '',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (outbox_id),
KEY idx_activity_template_stats_outbox_claim (status, next_retry_at_ms, lock_until_ms, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版统计事实 RocketMQ transactional outbox'`,
}
for _, statement := range statements {
if _, err := r.db.ExecContext(ctx, statement); err != nil {
return err
}
}
rewardSnapshotColumns := []struct {
table string
column string
statement string
}{
{"activity_template_task_progress", "reward_snapshot_id", `ALTER TABLE activity_template_task_progress ADD COLUMN reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '' AFTER reward_resource_group_id`},
{"activity_template_task_progress", "reward_snapshot_json", `ALTER TABLE activity_template_task_progress ADD COLUMN reward_snapshot_json JSON NULL AFTER reward_snapshot_id`},
{"activity_template_task_claims", "reward_snapshot_id", `ALTER TABLE activity_template_task_claims ADD COLUMN reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '' AFTER reward_resource_group_id`},
{"activity_template_task_claims", "reward_snapshot_json", `ALTER TABLE activity_template_task_claims ADD COLUMN reward_snapshot_json JSON NULL AFTER reward_snapshot_id`},
{"activity_template_daily_visits", "region_id", `ALTER TABLE activity_template_daily_visits ADD COLUMN region_id BIGINT NOT NULL DEFAULT 0 AFTER user_id`},
{"activity_template_task_progress", "first_region_id", `ALTER TABLE activity_template_task_progress ADD COLUMN first_region_id BIGINT NOT NULL DEFAULT 0 AFTER user_id`},
{"activity_template_task_progress", "earning_region_id", `ALTER TABLE activity_template_task_progress ADD COLUMN earning_region_id BIGINT NOT NULL DEFAULT 0 AFTER completed_at_ms`},
{"activity_template_task_progress", "attribution_at_ms", `ALTER TABLE activity_template_task_progress ADD COLUMN attribution_at_ms BIGINT NOT NULL DEFAULT 0 AFTER earning_region_id`},
{"activity_template_task_claims", "earning_region_id", `ALTER TABLE activity_template_task_claims ADD COLUMN earning_region_id BIGINT NOT NULL DEFAULT 0 AFTER user_id`},
{"activity_template_task_claims", "attribution_at_ms", `ALTER TABLE activity_template_task_claims ADD COLUMN attribution_at_ms BIGINT NOT NULL DEFAULT 0 AFTER earning_region_id`},
{"activity_template_reward_jobs", "reward_snapshot_id", `ALTER TABLE activity_template_reward_jobs ADD COLUMN reward_snapshot_id VARCHAR(96) NOT NULL DEFAULT '' AFTER resource_group_id`},
{"activity_template_reward_jobs", "reward_snapshot_json", `ALTER TABLE activity_template_reward_jobs ADD COLUMN reward_snapshot_json JSON NULL AFTER reward_snapshot_id`},
{"activity_template_reward_jobs", "max_attempts", `ALTER TABLE activity_template_reward_jobs ADD COLUMN max_attempts INT NOT NULL DEFAULT 8 COMMENT '自动投递总尝试上限dead 手工复活时只扩一轮预算' AFTER attempt_count`},
{"activity_template_reward_jobs", "next_retry_at_ms", `ALTER TABLE activity_template_reward_jobs ADD COLUMN next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次可领取时间UTC epoch ms' AFTER max_attempts`},
{"activity_template_reward_jobs", "locked_by", `ALTER TABLE activity_template_reward_jobs ADD COLUMN locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前 job lease owner' AFTER next_retry_at_ms`},
{"activity_template_reward_jobs", "lock_until_ms", `ALTER TABLE activity_template_reward_jobs ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'job lease 过期时间UTC epoch ms' AFTER locked_by`},
}
for _, addition := range rewardSnapshotColumns {
exists, err := r.columnExists(ctx, addition.table, addition.column)
if err != nil {
return err
}
if !exists {
if _, err := r.db.ExecContext(ctx, addition.statement); err != nil {
return err
}
}
}
// 旧环境可能已经执行过 011。新增水位列先以 0 落地,再只回填缺失值;历史周期一旦
// 物化便不随之后的配置变化漂移,保证结算审计可以复现当时的迟到容忍策略。
settleAfterExists, err := r.columnExists(ctx, "activity_template_rank_periods", "settle_after_ms")
if err != nil {
return err
}
if !settleAfterExists {
if _, err := r.db.ExecContext(ctx, `
ALTER TABLE activity_template_rank_periods
ADD COLUMN settle_after_ms BIGINT NOT NULL DEFAULT 0
COMMENT '结算水位周期结束加允许迟到窗口UTC epoch ms' AFTER period_end_ms`); err != nil {
return err
}
}
if _, err := r.db.ExecContext(ctx, `
UPDATE activity_template_rank_periods
SET settle_after_ms = period_end_ms + ?
WHERE settle_after_ms <= 0`, r.activityTemplateSettlementLatenessMS()); err != nil {
return err
}
settleDueIndexExists, err := r.indexExists(ctx, "activity_template_rank_periods", "idx_activity_template_period_settle_due")
if err != nil {
return err
}
if !settleDueIndexExists {
if _, err := r.db.ExecContext(ctx, `
CREATE INDEX idx_activity_template_period_settle_due
ON activity_template_rank_periods (app_code, status, settle_after_ms, lock_until_ms)`); err != nil {
return err
}
}
rewardClaimIndexExists, err := r.indexExists(ctx, "activity_template_reward_jobs", "idx_activity_template_reward_claim")
if err != nil {
return err
}
if !rewardClaimIndexExists {
if _, err := r.db.ExecContext(ctx, `
CREATE INDEX idx_activity_template_reward_claim
ON activity_template_reward_jobs (app_code, status, next_retry_at_ms, lock_until_ms, created_at_ms, reward_job_id)`); err != nil {
return err
}
}
taskClaimAdminIndexExists, err := r.indexExists(ctx, "activity_template_task_claims", "idx_activity_template_claim_admin")
if err != nil {
return err
}
if !taskClaimAdminIndexExists {
if _, err := r.db.ExecContext(ctx, `
CREATE INDEX idx_activity_template_claim_admin
ON activity_template_task_claims (app_code, template_id, version_no, task_day, task_key, user_id, status, updated_at_ms)`); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,126 @@
package mysql
import "context"
// ensureActivityTemplateTables mirrors deploy/mysql/migrations/010_activity_templates.sql for explicitly enabled local auto-migrate.
// Production still applies the numbered SQL migration so schema history remains reviewable and deterministic.
func (r *Repository) ensureActivityTemplateTables(ctx context.Context) error {
statements := []string{
`CREATE TABLE IF NOT EXISTS activity_templates (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
template_code VARCHAR(64) NOT NULL COMMENT 'App 内唯一业务编码',
name VARCHAR(128) NOT NULL COMMENT '后台内部名称',
activity_type VARCHAR(32) NOT NULL DEFAULT 'gift_challenge' COMMENT '活动类型',
status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/disabled/archived',
start_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动开始UTC epoch ms包含',
end_ms BIGINT NOT NULL DEFAULT 0 COMMENT '活动结束UTC epoch ms不包含',
all_regions BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否覆盖全部区域',
daily_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '日榜最多展示人数',
total_leaderboard_size INT NOT NULL DEFAULT 100 COMMENT '总榜最多展示人数',
display_config_json JSON NOT NULL COMMENT '客户端布局和样式配置',
revision BIGINT NOT NULL DEFAULT 1 COMMENT '后台编辑乐观锁版本',
published_version BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布快照版本',
created_by_admin_id BIGINT NOT NULL COMMENT '创建管理员',
updated_by_admin_id BIGINT NOT NULL COMMENT '最后更新管理员',
published_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布管理员',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
published_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近发布时间UTC epoch ms',
archived_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '软归档时间UTC epoch ms',
PRIMARY KEY (app_code, template_id),
UNIQUE KEY uk_activity_template_code (app_code, template_code),
KEY idx_activity_template_list (app_code, status, updated_at_ms, template_id),
KEY idx_activity_template_period (app_code, status, start_ms, end_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='可排期运营活动模版实例'`,
`CREATE TABLE IF NOT EXISTS activity_template_publish_locks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
updated_at_ms BIGINT NOT NULL COMMENT '最近获取发布锁时间UTC epoch ms',
PRIMARY KEY (app_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版发布事务互斥行'`,
`CREATE TABLE IF NOT EXISTS activity_template_regions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
region_id BIGINT NOT NULL COMMENT '投放区域 ID',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, region_id),
KEY idx_activity_template_region_match (app_code, region_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版投放区域'`,
`CREATE TABLE IF NOT EXISTS activity_template_locales (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
locale VARCHAR(24) NOT NULL COMMENT 'BCP-47 基础语言码',
title VARCHAR(160) NOT NULL DEFAULT '' COMMENT '客户端活动标题',
rules MEDIUMTEXT NOT NULL COMMENT '客户端活动规则正文',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版多语言文案'`,
`CREATE TABLE IF NOT EXISTS activity_template_gifts (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
gift_id VARCHAR(96) NOT NULL COMMENT 'wallet 礼物 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '发布展示名称快照',
icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '发布展示图标快照',
coin_price BIGINT NOT NULL DEFAULT 0 COMMENT '发布时金币价格快照仅供展示',
price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发布时价格版本快照',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, gift_id),
KEY idx_activity_template_gift_match (app_code, gift_id, template_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版指定礼物和展示快照'`,
`CREATE TABLE IF NOT EXISTS activity_template_tasks (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
task_key VARCHAR(64) NOT NULL COMMENT '模版内稳定任务键',
task_type VARCHAR(32) NOT NULL COMMENT 'daily_visit/gift_count/gift_coin_amount',
target_value BIGINT NOT NULL DEFAULT 0 COMMENT '任务达成阈值',
reward_resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, task_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版每日任务配置'`,
`CREATE TABLE IF NOT EXISTS activity_template_rank_rewards (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
board_type VARCHAR(16) NOT NULL COMMENT 'daily/total',
rank_from INT NOT NULL COMMENT '奖励起始名次闭区间',
rank_to INT NOT NULL COMMENT '奖励结束名次闭区间',
resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT 'wallet 奖励资源组 ID',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, board_type, rank_from, rank_to)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版日榜和总榜名次奖励'`,
`CREATE TABLE IF NOT EXISTS activity_template_assets (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
asset_key VARCHAR(64) NOT NULL COMMENT '素材位键',
locale VARCHAR(24) NOT NULL DEFAULT '*' COMMENT '* 表示共享素材否则为语言码',
url VARCHAR(1024) NOT NULL COMMENT 'COS/CDN 素材地址',
media_type VARCHAR(16) NOT NULL COMMENT 'image/webp/svga/pag',
sort_order INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, asset_key, locale)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版客户端素材'`,
`CREATE TABLE IF NOT EXISTS activity_template_versions (
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
template_id VARCHAR(96) NOT NULL COMMENT '活动模版 ID',
version_no BIGINT NOT NULL COMMENT '单模版递增发布版本',
snapshot_json JSON NOT NULL COMMENT '发布时完整不可变配置快照',
published_by_admin_id BIGINT NOT NULL COMMENT '发布管理员',
published_at_ms BIGINT NOT NULL COMMENT '发布时间UTC epoch ms',
PRIMARY KEY (app_code, template_id, version_no),
KEY idx_activity_template_version_list (app_code, template_id, published_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='活动模版不可变发布版本'`,
}
for _, statement := range statements {
if _, err := r.db.ExecContext(ctx, statement); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,245 @@
package mysql
import (
"context"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
var activityTemplateRewardLeaseTestColumns = []string{
"app_code", "reward_job_id", "template_id", "template_code", "version_no", "board_type", "period_key",
"rank_no", "user_id", "score", "resource_group_id", "reward_snapshot_id", "reward_snapshot_json",
"wallet_command_id", "wallet_grant_id", "status", "failure_reason", "attempt_count", "created_at_ms",
"updated_at_ms", "granted_at_ms", "max_attempts", "next_retry_at_ms", "locked_by", "lock_until_ms",
}
var activityTemplateRankPeriodLeaseTestColumns = []string{
"app_code", "template_id", "template_code", "version_no", "board_type", "period_key",
"period_start_ms", "period_end_ms", "settle_after_ms", "leaderboard_size", "status", "settled_at_ms",
}
func TestClaimDueActivityTemplateRankPeriodsExcludesMaterializedBlockedJobsBeforePending(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
const nowMS = int64(100_000)
lockUntilMS := nowMS + time.Minute.Milliseconds()
mock.ExpectBegin()
mock.ExpectQuery(`(?s)FROM activity_template_rank_periods p.+p.status = \?.+p.status = \?.+NOT EXISTS \(.+FROM activity_template_reward_jobs j.+LIMIT \? FOR UPDATE SKIP LOCKED`).
WithArgs("hyapp", nowMS, domain.PeriodStatusPending, domain.PeriodStatusSettling, nowMS, 50).
WillReturnRows(sqlmock.NewRows(activityTemplateRankPeriodLeaseTestColumns).
AddRow("hyapp", "pending-after-50-blocked", "summer", 3, domain.BoardTypeTotal, "total", 1, 2, 3, 100, domain.PeriodStatusPending, 0))
mock.ExpectExec(`(?s)UPDATE activity_template_rank_periods.+SET status = \?, locked_by = \?, lock_until_ms = \?.+WHERE app_code = \?`).
WithArgs(domain.PeriodStatusSettling, "settler", lockUntilMS, nowMS, "hyapp", "pending-after-50-blocked", int64(3), domain.BoardTypeTotal, "total").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()
periods, err := repository.ClaimDueActivityTemplateRankPeriods(ctx, "settler", nowMS, time.Minute, 50)
if err != nil {
t.Fatalf("ClaimDueActivityTemplateRankPeriods(): %v", err)
}
if len(periods) != 1 || periods[0].TemplateID != "pending-after-50-blocked" {
t.Fatalf("claimed periods = %+v, want only actionable pending period", periods)
}
if len(periods) == 50 {
t.Fatal("dead/backoff periods must not fill the discovery batch")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestClaimFinalizableActivityTemplateRankPeriodsOnlySelectsAllGrantedRewardingPeriods(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
const nowMS = int64(200_000)
mock.ExpectBegin()
mock.ExpectQuery(`(?s)FROM activity_template_rank_periods p.+p.status = \?.+p.status = \? AND EXISTS \(.+FROM activity_template_reward_jobs materialized.+NOT EXISTS \(.+FROM activity_template_reward_jobs j.+j.status <> \?.+LIMIT \? FOR UPDATE SKIP LOCKED`).
WithArgs("hyapp", nowMS, domain.PeriodStatusRewarding, domain.PeriodStatusSettling, domain.ClaimStatusGranted, 50).
WillReturnRows(sqlmock.NewRows(activityTemplateRankPeriodLeaseTestColumns))
mock.ExpectCommit()
periods, err := repository.ClaimFinalizableActivityTemplateRankPeriods(ctx, "finalizer", nowMS, time.Minute, 50)
if err != nil {
t.Fatalf("ClaimFinalizableActivityTemplateRankPeriods(): %v", err)
}
if len(periods) != 0 {
t.Fatalf("finalizable periods = %+v, want none while jobs are blocked", periods)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestClaimActivityTemplateRewardJobsUsesFiniteLeasesAndRecoversExpiredRunning(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
nowMS := int64(1_000)
lockUntilMS := nowMS + (30 * time.Second).Milliseconds()
mock.ExpectBegin()
rows := sqlmock.NewRows(activityTemplateRewardLeaseTestColumns).
AddRow("hyapp", "job_failed", "tpl_1", "summer", 2, "daily", "2026-07-15", 1, 1001, 900, 81, "", "null", "wallet-stable-failed", "", domain.ClaimStatusFailed, "wallet unavailable", 1, 100, 900, 0, 8, 900, "", 0).
AddRow("hyapp", "job_crashed", "tpl_1", "summer", 2, "daily", "2026-07-15", 2, 1002, 800, 82, "", "null", "wallet-stable-crashed", "", domain.ClaimStatusRunning, "", 2, 100, 900, 0, 8, 0, "dead-worker", 999).
AddRow("hyapp", "job_exhausted", "tpl_1", "summer", 2, "daily", "2026-07-15", 3, 1003, 700, 83, "", "null", "wallet-stable-exhausted", "", domain.ClaimStatusRunning, "", 8, 100, 900, 0, 8, 0, "dead-worker", 999)
mock.ExpectQuery(`(?s)SELECT app_code, reward_job_id.+FROM activity_template_reward_jobs.+FOR UPDATE SKIP LOCKED`).
WithArgs("hyapp", domain.ClaimStatusPending, domain.ClaimStatusFailed, nowMS, domain.ClaimStatusRunning, nowMS, 3).
WillReturnRows(rows)
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+attempt_count = attempt_count \+ 1.+WHERE app_code = \? AND reward_job_id = \?`).
WithArgs(domain.ClaimStatusRunning, "settler-a", lockUntilMS, nowMS, "hyapp", "job_failed").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+attempt_count = attempt_count \+ 1.+WHERE app_code = \? AND reward_job_id = \?`).
WithArgs(domain.ClaimStatusRunning, "settler-a", lockUntilMS, nowMS, "hyapp", "job_crashed").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+failure_reason = CASE WHEN failure_reason = ''.+WHERE app_code = \? AND reward_job_id = \?`).
WithArgs(activityTemplateRewardJobStatusDead, "maximum delivery attempts exhausted", nowMS, "hyapp", "job_exhausted").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()
jobs, err := repository.ClaimActivityTemplateRewardJobs(ctx, "settler-a", nowMS, 30*time.Second, 3)
if err != nil {
t.Fatalf("ClaimActivityTemplateRewardJobs(): %v", err)
}
if len(jobs) != 2 {
t.Fatalf("claimed jobs = %+v, want failed and expired-running jobs only", jobs)
}
if jobs[0].RewardJobID != "job_failed" || jobs[0].AttemptCount != 2 || jobs[0].WalletCommandID != "wallet-stable-failed" {
t.Fatalf("failed retry claim = %+v", jobs[0])
}
if jobs[1].RewardJobID != "job_crashed" || jobs[1].AttemptCount != 3 || jobs[1].WalletCommandID != "wallet-stable-crashed" {
t.Fatalf("expired running recovery = %+v", jobs[1])
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestMarkActivityTemplateRewardJobFailedWithLeaseSchedulesStableBackoff(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
nowMS := int64(20_000)
nextRetryAtMS := activityTemplateRewardRetryAtMS("job_retry", 2, nowMS)
mock.ExpectBegin()
mock.ExpectQuery(`(?s)SELECT attempt_count, max_attempts FROM activity_template_reward_jobs.+FOR UPDATE`).
WithArgs("hyapp", "job_retry", domain.ClaimStatusRunning, "settler-b", nowMS).
WillReturnRows(sqlmock.NewRows([]string{"attempt_count", "max_attempts"}).AddRow(2, 8))
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+next_retry_at_ms = \?.+locked_by = ''.+lock_until_ms = 0.+locked_by = \?.+lock_until_ms > \?`).
WithArgs(domain.ClaimStatusFailed, "wallet timeout", nextRetryAtMS, nowMS, "hyapp", "job_retry", domain.ClaimStatusRunning, "settler-b", nowMS).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()
if err := repository.MarkActivityTemplateRewardJobFailedWithLease(ctx, "job_retry", "settler-b", "wallet timeout", nowMS); err != nil {
t.Fatalf("MarkActivityTemplateRewardJobFailedWithLease(): %v", err)
}
if nextRetryAtMS <= nowMS+activityTemplateRewardJobRetryBase.Milliseconds() {
t.Fatalf("next retry = %d, want exponential delay plus jitter after %d", nextRetryAtMS, nowMS)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestMarkActivityTemplateRewardJobGrantedRejectsLostLeaseOwner(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
nowMS := int64(50_000)
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+status = \?.+locked_by = ''.+WHERE app_code = \?.+locked_by = \?.+lock_until_ms > \?`).
WithArgs(domain.ClaimStatusGranted, "grant_1", nowMS, nowMS, "hyapp", "job_taken_over", domain.ClaimStatusRunning, "stale-worker", nowMS).
WillReturnResult(sqlmock.NewResult(0, 0))
err = repository.MarkActivityTemplateRewardJobGrantedWithLease(ctx, "job_taken_over", "stale-worker", "grant_1", nowMS)
if xerr.CodeOf(err) != xerr.Conflict {
t.Fatalf("lost owner error = %v, want conflict", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestClaimActivityTemplateRewardJobForManualRetryRevivesDeadWithoutChangingWalletCommand(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New(): %v", err)
}
t.Cleanup(func() { _ = db.Close() })
repository := &Repository{db: db}
ctx := appcode.WithContext(context.Background(), "hyapp")
nowMS := int64(80_000)
lockUntilMS := nowMS + time.Minute.Milliseconds()
mock.ExpectBegin()
rows := sqlmock.NewRows(activityTemplateRewardLeaseTestColumns).
AddRow("hyapp", "job_dead", "tpl_2", "anniversary", 4, "total", "total", 1, 2001, 9_000, 91, "", "null", "wallet-command-never-changes", "", activityTemplateRewardJobStatusDead, "attempts exhausted", 8, 100, 79_000, 0, 8, 0, "", 0)
mock.ExpectQuery(`(?s)SELECT app_code, reward_job_id.+FROM activity_template_reward_jobs.+FOR UPDATE`).
WithArgs("hyapp", "job_dead").WillReturnRows(rows)
mock.ExpectExec(`(?s)UPDATE activity_template_reward_jobs.+max_attempts = \?.+locked_by = \?.+lock_until_ms = \?.+status IN \(\?, \?\)`).
WithArgs(domain.ClaimStatusRunning, int32(9), "admin-retry-90001", lockUntilMS, nowMS, "hyapp", "job_dead", domain.ClaimStatusFailed, activityTemplateRewardJobStatusDead).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()
job, err := repository.ClaimActivityTemplateRewardJobForManualRetry(ctx, "tpl_2", "job_dead", "admin-retry-90001", nowMS, time.Minute)
if err != nil {
t.Fatalf("ClaimActivityTemplateRewardJobForManualRetry(): %v", err)
}
if job.Status != domain.ClaimStatusRunning || job.AttemptCount != 9 || job.MaxAttempts != 9 || job.NextRetryAtMS != 0 ||
job.WalletCommandID != "wallet-command-never-changes" {
t.Fatalf("manual retry job = %+v", job)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations: %v", err)
}
}
func TestActivityTemplateRewardRetryBackoffIsDeterministicBoundedAndIncreasing(t *testing.T) {
nowMS := int64(1_000_000)
previous := nowMS
for attempt := int32(1); attempt <= activityTemplateRewardJobDefaultAttempts; attempt++ {
first := activityTemplateRewardRetryAtMS("job-stable", attempt, nowMS)
second := activityTemplateRewardRetryAtMS("job-stable", attempt, nowMS)
if first != second {
t.Fatalf("attempt %d jitter is not deterministic: %d != %d", attempt, first, second)
}
if first <= previous {
t.Fatalf("attempt %d retry %d did not increase after %d", attempt, first, previous)
}
if first-nowMS > activityTemplateRewardJobRetryHardCap.Milliseconds() {
t.Fatalf("attempt %d delay %d exceeds hard cap", attempt, first-nowMS)
}
previous = first
}
if capped := activityTemplateRewardRetryAtMS("job-stable", 100, nowMS) - nowMS; capped > activityTemplateRewardJobRetryHardCap.Milliseconds() {
t.Fatalf("high-attempt delay %d exceeds hard cap", capped)
}
}

View File

@ -0,0 +1,100 @@
package mysql
import (
"context"
"database/sql"
"time"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
func (r *Repository) ClaimActivityTemplateStatsOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.StatsOutboxRecord, error) {
if r == nil || r.db == nil {
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
if batchSize <= 0 {
batchSize = 100
}
if lockTTL <= 0 {
lockTTL = 30 * time.Second
}
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
rows, err := tx.QueryContext(ctx, `
SELECT outbox_id, event_type, payload, retry_count
FROM activity_template_stats_outbox
WHERE ((status IN (?, ?) AND next_retry_at_ms <= ?) OR (status = ? AND lock_until_ms <= ?))
ORDER BY created_at_ms, outbox_id
LIMIT ? FOR UPDATE SKIP LOCKED`, outboxStatusPending, outboxStatusFailed, nowMS, outboxStatusRunning, nowMS, batchSize)
if err != nil {
return nil, err
}
items := make([]domain.StatsOutboxRecord, 0, batchSize)
for rows.Next() {
var item domain.StatsOutboxRecord
if err := rows.Scan(&item.OutboxID, &item.EventType, &item.Payload, &item.RetryCount); err != nil {
rows.Close()
return nil, err
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
rows.Close()
return nil, err
}
if err := rows.Close(); err != nil {
return nil, err
}
lockUntil := nowMS + lockTTL.Milliseconds()
for _, item := range items {
if _, err := tx.ExecContext(ctx, `
UPDATE activity_template_stats_outbox
SET status = ?, locked_by = ?, lock_until_ms = ?, updated_at_ms = ?
WHERE outbox_id = ?`, outboxStatusRunning, workerID, lockUntil, nowMS, item.OutboxID); err != nil {
return nil, err
}
}
if err := tx.Commit(); err != nil {
return nil, err
}
return items, nil
}
func (r *Repository) MarkActivityTemplateStatsOutboxSent(ctx context.Context, outboxID string, nowMS int64) error {
_, err := r.db.ExecContext(ctx, `
UPDATE activity_template_stats_outbox
SET status = ?, locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ?
WHERE outbox_id = ?`, outboxStatusSent, nowMS, outboxID)
return err
}
func (r *Repository) MarkActivityTemplateStatsOutboxFailed(ctx context.Context, outboxID, reason string, nowMS int64) error {
var retryCount int32
err := r.db.QueryRowContext(ctx, `SELECT retry_count FROM activity_template_stats_outbox WHERE outbox_id = ?`, outboxID).Scan(&retryCount)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
return err
}
retryCount++
backoff := time.Second * time.Duration(1<<minInt32(retryCount, 8))
_, err = r.db.ExecContext(ctx, `
UPDATE activity_template_stats_outbox
SET status = ?, retry_count = ?, next_retry_at_ms = ?, locked_by = '', lock_until_ms = 0,
last_error = ?, updated_at_ms = ?
WHERE outbox_id = ?`, outboxStatusFailed, retryCount, nowMS+backoff.Milliseconds(),
truncateActivityTemplateRuntimeError(reason), nowMS, outboxID)
return err
}
func minInt32(left, right int32) int32 {
if left < right {
return left
}
return right
}

View File

@ -17,6 +17,26 @@ import (
// Repository 是 activity-service 的 MySQL 存储入口。 // Repository 是 activity-service 的 MySQL 存储入口。
type Repository struct { type Repository struct {
db *sql.DB db *sql.DB
activityTemplateSettlementLateness time.Duration
}
const defaultActivityTemplateSettlementLateness = 10 * time.Minute
func (r *Repository) SetActivityTemplateSettlementLateness(value time.Duration) {
if r == nil {
return
}
if value <= 0 {
value = defaultActivityTemplateSettlementLateness
}
r.activityTemplateSettlementLateness = value
}
func (r *Repository) activityTemplateSettlementLatenessMS() int64 {
if r == nil || r.activityTemplateSettlementLateness <= 0 {
return defaultActivityTemplateSettlementLateness.Milliseconds()
}
return r.activityTemplateSettlementLateness.Milliseconds()
} }
// Open 创建 MySQL 连接池并做启动 ping。 // Open 创建 MySQL 连接池并做启动 ping。
@ -63,6 +83,12 @@ func (r *Repository) Migrate(ctx context.Context) error {
if err := r.ensureAgencyOpeningTables(ctx); err != nil { if err := r.ensureAgencyOpeningTables(ctx); err != nil {
return err return err
} }
if err := r.ensureActivityTemplateTables(ctx); err != nil {
return err
}
if err := r.ensureActivityTemplateRuntimeTables(ctx); err != nil {
return err
}
if err := r.ensureWheelTables(ctx); err != nil { if err := r.ensureWheelTables(ctx); err != nil {
return err return err
} }

View File

@ -0,0 +1,84 @@
package grpc
import (
"context"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
func (s *AdminActivityTemplateServer) ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.AdminListActivityTemplateLeaderboardRequest) (*activityv1.AdminListActivityTemplateLeaderboardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.AdminListRuntimeLeaderboard(ctx, domain.LeaderboardQuery{
TemplateID: req.GetTemplateId(), VersionNo: req.GetVersionNo(), BoardType: req.GetBoardType(),
PeriodKey: req.GetPeriodKey(), Page: req.GetPage(), PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.AdminListActivityTemplateLeaderboardResponse{
Period: &activityv1.ActivityTemplateRankPeriod{
TemplateId: result.Period.TemplateID, TemplateCode: result.Period.TemplateCode,
VersionNo: result.Period.VersionNo, BoardType: result.Period.BoardType, PeriodKey: result.Period.PeriodKey,
PeriodStartMs: result.Period.StartMS, PeriodEndMs: result.Period.EndMS,
Status: result.Period.Status, SettledAtMs: result.Period.SettledAtMS, SettleAfterMs: result.Period.SettleAfterMS,
},
Entries: make([]*activityv1.ActivityTemplateLeaderboardEntry, 0, len(result.Entries)), Total: result.Total,
}
for _, item := range result.Entries {
resp.Entries = append(resp.Entries, activityTemplateLeaderboardEntryToProto(item))
}
return resp, nil
}
func (s *AdminActivityTemplateServer) ListActivityTemplateRewardJobs(ctx context.Context, req *activityv1.ListActivityTemplateRewardJobsRequest) (*activityv1.ListActivityTemplateRewardJobsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
items, total, err := s.svc.ListRewardJobs(ctx, domain.RewardJobQuery{
TemplateID: req.GetTemplateId(), VersionNo: req.GetVersionNo(), BoardType: req.GetBoardType(),
PeriodKey: req.GetPeriodKey(), Status: req.GetStatus(), Page: req.GetPage(), PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ListActivityTemplateRewardJobsResponse{Jobs: make([]*activityv1.ActivityTemplateRewardJob, 0, len(items)), Total: total}
for _, item := range items {
resp.Jobs = append(resp.Jobs, activityTemplateRewardJobToProto(item))
}
return resp, nil
}
func (s *AdminActivityTemplateServer) RetryActivityTemplateRewardJob(ctx context.Context, req *activityv1.RetryActivityTemplateRewardJobRequest) (*activityv1.RetryActivityTemplateRewardJobResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.RetryRewardJob(ctx, req.GetTemplateId(), req.GetRewardJobId(), req.GetOperatorAdminId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.RetryActivityTemplateRewardJobResponse{Job: activityTemplateRewardJobToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) ListActivityTemplateTaskClaims(ctx context.Context, req *activityv1.ListActivityTemplateTaskClaimsRequest) (*activityv1.ListActivityTemplateTaskClaimsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
items, total, err := s.svc.ListTaskClaims(ctx, domain.TaskClaimQuery{
TemplateID: req.GetTemplateId(), VersionNo: req.GetVersionNo(), TaskDay: req.GetTaskDay(), TaskKey: req.GetTaskKey(),
UserID: req.GetUserId(), Status: req.GetStatus(), Page: req.GetPage(), PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ListActivityTemplateTaskClaimsResponse{Claims: make([]*activityv1.ActivityTemplateTaskClaim, 0, len(items)), Total: total}
for _, item := range items {
resp.Claims = append(resp.Claims, activityTemplateTaskClaimToProto(item))
}
return resp, nil
}
func (s *AdminActivityTemplateServer) RetryActivityTemplateTaskClaim(ctx context.Context, req *activityv1.RetryActivityTemplateTaskClaimRequest) (*activityv1.RetryActivityTemplateTaskClaimResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.RetryTaskClaim(ctx, req.GetTemplateId(), req.GetClaimId(), req.GetOperatorAdminId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.RetryActivityTemplateTaskClaimResponse{Claim: activityTemplateTaskClaimToProto(item)}, nil
}

View File

@ -0,0 +1,61 @@
package grpc
import (
"testing"
"time"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
)
func TestActivityTemplateRuntimeResponseFillsActualLifecycle(t *testing.T) {
nowMS := time.Now().UTC().UnixMilli()
tests := []struct {
name string
runtime domain.Runtime
want string
}{
{
name: "scheduled",
runtime: domain.Runtime{
Template: domain.Template{TemplateID: "scheduled", Status: domain.StatusPublished, StartMS: nowMS + 60_000, EndMS: nowMS + 120_000},
VersionNo: 1, RuntimeFromMS: nowMS + 60_000,
},
want: domain.LifecycleScheduled,
},
{
name: "ongoing",
runtime: domain.Runtime{
Template: domain.Template{TemplateID: "ongoing", Status: domain.StatusPublished, StartMS: nowMS - 60_000, EndMS: nowMS + 60_000},
VersionNo: 2, RuntimeFromMS: nowMS - 60_000,
},
want: domain.LifecycleOngoing,
},
{
name: "ended",
runtime: domain.Runtime{
Template: domain.Template{TemplateID: "ended", Status: domain.StatusPublished, StartMS: nowMS - 120_000, EndMS: nowMS - 60_000},
VersionNo: 3, RuntimeFromMS: nowMS - 120_000,
},
want: domain.LifecycleEnded,
},
{
name: "disabled",
runtime: domain.Runtime{
Template: domain.Template{TemplateID: "disabled", Status: domain.StatusPublished, StartMS: nowMS - 120_000, EndMS: nowMS + 120_000},
VersionNo: 4, RuntimeFromMS: nowMS - 120_000, RuntimeToMS: nowMS - 60_000,
},
want: domain.LifecycleDisabled,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := activityTemplateRuntimeToProto(tt.runtime)
if got == nil || got.GetTemplate().GetLifecycleStatus() != tt.want {
t.Fatalf("runtime response lifecycle = %+v, want %q", got, tt.want)
}
if got.GetRuntimeFromMs() != tt.runtime.RuntimeFromMS || got.GetRuntimeToMs() != tt.runtime.RuntimeToMS || got.GetServerTimeMs() <= 0 {
t.Fatalf("runtime response window/server time = %+v", got)
}
})
}
}

View File

@ -0,0 +1,166 @@
package grpc
import (
"context"
"time"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
service "hyapp/services/activity-service/internal/service/activitytemplate"
)
type ActivityTemplateRuntimeServer struct {
activityv1.UnimplementedActivityTemplateRuntimeServiceServer
svc *service.Service
}
func NewActivityTemplateRuntimeServer(svc *service.Service) *ActivityTemplateRuntimeServer {
return &ActivityTemplateRuntimeServer{svc: svc}
}
func (s *ActivityTemplateRuntimeServer) GetCurrentActivityTemplate(ctx context.Context, req *activityv1.GetCurrentActivityTemplateRequest) (*activityv1.GetCurrentActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
runtime, found, err := s.svc.GetCurrentRuntime(ctx, req.GetRegionId(), "")
if err != nil {
return nil, xerr.ToGRPCError(err)
}
if !found {
return &activityv1.GetCurrentActivityTemplateResponse{Found: false}, nil
}
return &activityv1.GetCurrentActivityTemplateResponse{Runtime: activityTemplateRuntimeToProto(runtime), Found: true}, nil
}
func (s *ActivityTemplateRuntimeServer) VisitActivityTemplate(ctx context.Context, req *activityv1.VisitActivityTemplateRequest) (*activityv1.VisitActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.Visit(ctx, domain.VisitCommand{
UserID: req.GetUserId(), RegionID: req.GetRegionId(), TemplateCode: req.GetTemplateCode(), CommandID: req.GetCommandId(), VersionNo: req.GetVersionNo(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.VisitActivityTemplateResponse{
Runtime: activityTemplateRuntimeToProto(result.Runtime), Tasks: activityTemplateTasksToProto(result.Tasks), FirstVisit: result.FirstVisit,
}, nil
}
func (s *ActivityTemplateRuntimeServer) ListActivityTemplateTasks(ctx context.Context, req *activityv1.ListActivityTemplateTasksRequest) (*activityv1.ListActivityTemplateTasksResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
runtime, items, nextRefreshMS, err := s.svc.ListRuntimeTasks(ctx, req.GetUserId(), req.GetRegionId(), req.GetVersionNo(), req.GetTemplateCode(), req.GetTaskDay())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.ListActivityTemplateTasksResponse{
Runtime: activityTemplateRuntimeToProto(runtime), Tasks: activityTemplateTasksToProto(items), NextRefreshAtMs: nextRefreshMS,
}, nil
}
func (s *ActivityTemplateRuntimeServer) ClaimActivityTemplateTaskReward(ctx context.Context, req *activityv1.ClaimActivityTemplateTaskRewardRequest) (*activityv1.ClaimActivityTemplateTaskRewardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
claim, err := s.svc.ClaimRuntimeTaskReward(ctx, domain.ClaimCommand{
UserID: req.GetUserId(), RegionID: req.GetRegionId(), TemplateCode: req.GetTemplateCode(), VersionNo: req.GetVersionNo(), TaskDay: req.GetTaskDay(),
TaskKey: req.GetTaskKey(), CommandID: req.GetCommandId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.ClaimActivityTemplateTaskRewardResponse{Claim: activityTemplateTaskClaimToProto(claim), Claimed: claim.Status == domain.ClaimStatusGranted}, nil
}
func (s *ActivityTemplateRuntimeServer) ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.ListActivityTemplateLeaderboardRequest) (*activityv1.ListActivityTemplateLeaderboardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.ListRuntimeLeaderboard(ctx, domain.LeaderboardQuery{
UserID: req.GetUserId(), RegionID: req.GetRegionId(), TemplateCode: req.GetTemplateCode(), VersionNo: req.GetVersionNo(), BoardType: req.GetBoardType(),
PeriodKey: req.GetPeriodKey(), Page: req.GetPage(), PageSize: req.GetPageSize(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return activityTemplateLeaderboardResultToProto(result), nil
}
func activityTemplateRuntimeToProto(item domain.Runtime) *activityv1.ActivityTemplateRuntime {
if item.Template.TemplateID == "" {
return nil
}
nowMS := time.Now().UTC().UnixMilli()
item.Template.LifecycleStatus = service.RuntimeLifecycleStatus(item.Template, item.RuntimeFromMS, item.RuntimeToMS, nowMS)
return &activityv1.ActivityTemplateRuntime{
Template: activityTemplateToProto(item.Template), VersionNo: item.VersionNo,
RuntimeFromMs: item.RuntimeFromMS, RuntimeToMs: item.RuntimeToMS, ServerTimeMs: nowMS,
}
}
func activityTemplateTasksToProto(items []domain.TaskProgress) []*activityv1.ActivityTemplateRuntimeTask {
out := make([]*activityv1.ActivityTemplateRuntimeTask, 0, len(items))
for _, item := range items {
out = append(out, &activityv1.ActivityTemplateRuntimeTask{
TemplateId: item.TemplateID, TemplateCode: item.TemplateCode, VersionNo: item.VersionNo,
TaskDay: item.TaskDay, TaskKey: item.TaskKey, TaskType: item.TaskType, TargetValue: item.TargetValue,
ProgressValue: item.ProgressValue, RewardResourceGroupId: item.RewardResourceGroupID,
Status: item.Status, Claimable: item.Status == domain.TaskStatusCompleted,
CompletedAtMs: item.CompletedAtMS, ClaimedAtMs: item.ClaimedAtMS, SortOrder: item.SortOrder,
RewardSnapshotId: item.RewardSnapshot.SnapshotID, RewardSnapshotHash: item.RewardSnapshot.SnapshotHash,
RewardName: item.RewardSnapshot.Name, RewardItems: activityTemplateRewardItemsToProto(item.RewardSnapshot.Items),
})
}
return out
}
func activityTemplateTaskClaimToProto(item domain.TaskClaim) *activityv1.ActivityTemplateTaskClaim {
if item.ClaimID == "" {
return nil
}
return &activityv1.ActivityTemplateTaskClaim{
ClaimId: item.ClaimID, CommandId: item.CommandID, TemplateId: item.TemplateID, TemplateCode: item.TemplateCode,
VersionNo: item.VersionNo, TaskDay: item.TaskDay, TaskKey: item.TaskKey, UserId: item.UserID,
RewardResourceGroupId: item.RewardResourceGroupID, WalletCommandId: item.WalletCommandID,
WalletGrantId: item.WalletGrantID, Status: item.Status, FailureReason: item.FailureReason,
AttemptCount: item.AttemptCount, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, GrantedAtMs: item.GrantedAtMS,
RewardSnapshotId: item.RewardSnapshot.SnapshotID, RewardName: item.RewardSnapshot.Name,
RewardSnapshotHash: item.RewardSnapshot.SnapshotHash, EarningRegionId: item.EarningRegionID,
AttributionAtMs: item.AttributionAtMS,
RewardItems: activityTemplateRewardItemsToProto(item.RewardSnapshot.Items),
}
}
func activityTemplateLeaderboardResultToProto(result domain.LeaderboardResult) *activityv1.ListActivityTemplateLeaderboardResponse {
resp := &activityv1.ListActivityTemplateLeaderboardResponse{
Runtime: activityTemplateRuntimeToProto(result.Runtime), BoardType: result.Period.BoardType, PeriodKey: result.Period.PeriodKey,
PeriodStartMs: result.Period.StartMS, PeriodEndMs: result.Period.EndMS, PeriodStatus: result.Period.Status,
Entries: make([]*activityv1.ActivityTemplateLeaderboardEntry, 0, len(result.Entries)), Total: result.Total,
SettleAfterMs: result.Period.SettleAfterMS,
}
for _, item := range result.Entries {
resp.Entries = append(resp.Entries, activityTemplateLeaderboardEntryToProto(item))
}
if result.MyFound {
resp.MyEntry = activityTemplateLeaderboardEntryToProto(result.MyEntry)
}
return resp
}
func activityTemplateLeaderboardEntryToProto(item domain.LeaderboardEntry) *activityv1.ActivityTemplateLeaderboardEntry {
return &activityv1.ActivityTemplateLeaderboardEntry{
RankNo: item.RankNo, UserId: item.UserID, Score: item.Score, GiftCount: item.GiftCount,
CoinAmount: item.CoinAmount, FirstScoredAtMs: item.FirstScoredAtMS, Snapshot: item.Snapshot,
}
}
func activityTemplateRewardJobToProto(item domain.RewardJob) *activityv1.ActivityTemplateRewardJob {
if item.RewardJobID == "" {
return nil
}
return &activityv1.ActivityTemplateRewardJob{
RewardJobId: item.RewardJobID, TemplateId: item.TemplateID, TemplateCode: item.TemplateCode,
VersionNo: item.VersionNo, BoardType: item.BoardType, PeriodKey: item.PeriodKey,
RankNo: item.RankNo, UserId: item.UserID, Score: item.Score, ResourceGroupId: item.ResourceGroupID,
WalletCommandId: item.WalletCommandID, WalletGrantId: item.WalletGrantID, Status: item.Status,
FailureReason: item.FailureReason, AttemptCount: item.AttemptCount, CreatedAtMs: item.CreatedAtMS,
UpdatedAtMs: item.UpdatedAtMS, GrantedAtMs: item.GrantedAtMS,
RewardSnapshotId: item.RewardSnapshot.SnapshotID, RewardSnapshotHash: item.RewardSnapshot.SnapshotHash,
RewardName: item.RewardSnapshot.Name, RewardItems: activityTemplateRewardItemsToProto(item.RewardSnapshot.Items),
MaxAttempts: item.MaxAttempts, NextRetryAtMs: item.NextRetryAtMS,
}
}

View File

@ -0,0 +1,325 @@
package grpc
import (
"context"
"time"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
domain "hyapp/services/activity-service/internal/domain/activitytemplate"
service "hyapp/services/activity-service/internal/service/activitytemplate"
)
// AdminActivityTemplateServer 只把 protobuf 转为领域命令;发布规则、版本和事务都由 service/repository 拥有。
type AdminActivityTemplateServer struct {
activityv1.UnimplementedAdminActivityTemplateServiceServer
svc *service.Service
}
func NewAdminActivityTemplateServer(svc *service.Service) *AdminActivityTemplateServer {
return &AdminActivityTemplateServer{svc: svc}
}
func (s *AdminActivityTemplateServer) ListActivityTemplates(ctx context.Context, req *activityv1.ListActivityTemplatesRequest) (*activityv1.ListActivityTemplatesResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
query := domain.ListQuery{
Keyword: req.GetKeyword(),
Status: req.GetStatus(),
StartMS: req.GetStartMs(),
EndMS: req.GetEndMs(),
Page: req.GetPage(),
PageSize: req.GetPageSize(),
}
if req.RegionId != nil {
value := req.GetRegionId()
query.RegionID = &value
}
if req.AllRegions != nil {
value := req.GetAllRegions()
query.AllRegions = &value
}
items, total, err := s.svc.List(ctx, query)
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ListActivityTemplatesResponse{
Templates: make([]*activityv1.ActivityTemplateSummary, 0, len(items)),
Total: total,
ServerTimeMs: time.Now().UTC().UnixMilli(),
}
for _, item := range items {
resp.Templates = append(resp.Templates, activityTemplateSummaryToProto(item))
}
return resp, nil
}
func (s *AdminActivityTemplateServer) CreateActivityTemplate(ctx context.Context, req *activityv1.CreateActivityTemplateRequest) (*activityv1.CreateActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.Create(ctx, activityTemplateFromProto(req.GetTemplate()), req.GetOperatorAdminId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.CreateActivityTemplateResponse{Template: activityTemplateToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) GetActivityTemplate(ctx context.Context, req *activityv1.GetActivityTemplateRequest) (*activityv1.GetActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.Get(ctx, req.GetTemplateId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.GetActivityTemplateResponse{Template: activityTemplateToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) UpdateActivityTemplate(ctx context.Context, req *activityv1.UpdateActivityTemplateRequest) (*activityv1.UpdateActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.Update(ctx, domain.UpdateCommand{
Template: activityTemplateFromProto(req.GetTemplate()),
ExpectedRevision: req.GetExpectedRevision(),
OperatorAdminID: req.GetOperatorAdminId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.UpdateActivityTemplateResponse{Template: activityTemplateToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) SetActivityTemplateStatus(ctx context.Context, req *activityv1.SetActivityTemplateStatusRequest) (*activityv1.SetActivityTemplateStatusResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.SetStatus(ctx, domain.StatusCommand{
TemplateID: req.GetTemplateId(),
Status: req.GetStatus(),
ExpectedRevision: req.GetExpectedRevision(),
OperatorAdminID: req.GetOperatorAdminId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.SetActivityTemplateStatusResponse{Template: activityTemplateToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) DeleteActivityTemplate(ctx context.Context, req *activityv1.DeleteActivityTemplateRequest) (*activityv1.DeleteActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, archived, err := s.svc.Delete(ctx, domain.StatusCommand{
TemplateID: req.GetTemplateId(),
ExpectedRevision: req.GetExpectedRevision(),
OperatorAdminID: req.GetOperatorAdminId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.DeleteActivityTemplateResponse{Template: activityTemplateToProto(item), Archived: archived}, nil
}
func (s *AdminActivityTemplateServer) ListActivityTemplateVersions(ctx context.Context, req *activityv1.ListActivityTemplateVersionsRequest) (*activityv1.ListActivityTemplateVersionsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
items, total, err := s.svc.ListVersions(ctx, req.GetTemplateId(), req.GetPage(), req.GetPageSize())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ListActivityTemplateVersionsResponse{Versions: make([]*activityv1.ActivityTemplateVersion, 0, len(items)), Total: total}
for _, item := range items {
resp.Versions = append(resp.Versions, &activityv1.ActivityTemplateVersion{
TemplateId: item.TemplateID,
VersionNo: item.VersionNo,
Snapshot: activityTemplateToProto(item.Snapshot),
PublishedByAdminId: item.PublishedByAdminID,
PublishedAtMs: item.PublishedAtMS,
RuntimeFromMs: item.RuntimeFromMS,
RuntimeToMs: item.RuntimeToMS,
})
}
return resp, nil
}
func (s *AdminActivityTemplateServer) CloneActivityTemplate(ctx context.Context, req *activityv1.CloneActivityTemplateRequest) (*activityv1.CloneActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
item, err := s.svc.Clone(ctx, domain.CloneCommand{
SourceTemplateID: req.GetSourceTemplateId(),
SourceVersion: req.GetSourceVersion(),
TemplateCode: req.GetTemplateCode(),
Name: req.GetName(),
OperatorAdminID: req.GetOperatorAdminId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.CloneActivityTemplateResponse{Template: activityTemplateToProto(item)}, nil
}
func (s *AdminActivityTemplateServer) ValidateActivityTemplate(ctx context.Context, req *activityv1.ValidateActivityTemplateRequest) (*activityv1.ValidateActivityTemplateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
issues, err := s.svc.Validate(ctx, activityTemplateFromProto(req.GetTemplate()), req.GetForPublish())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &activityv1.ValidateActivityTemplateResponse{Valid: len(issues) == 0, Issues: make([]*activityv1.ActivityTemplateValidationIssue, 0, len(issues))}
for _, issue := range issues {
resp.Issues = append(resp.Issues, &activityv1.ActivityTemplateValidationIssue{Field: issue.Field, Code: issue.Code, Message: issue.Message})
}
return resp, nil
}
func activityTemplateFromProto(item *activityv1.ActivityTemplate) domain.Template {
if item == nil {
return domain.Template{}
}
out := domain.Template{
AppCode: item.GetAppCode(),
TemplateID: item.GetTemplateId(),
TemplateCode: item.GetTemplateCode(),
Name: item.GetName(),
ActivityType: item.GetActivityType(),
Status: item.GetStatus(),
LifecycleStatus: item.GetLifecycleStatus(),
StartMS: item.GetStartMs(),
EndMS: item.GetEndMs(),
AllRegions: item.GetAllRegions(),
RegionIDs: append([]int64(nil), item.GetRegionIds()...),
DailyLeaderboardSize: item.GetDailyLeaderboardSize(),
TotalLeaderboardSize: item.GetTotalLeaderboardSize(),
DisplayConfigJSON: item.GetDisplayConfigJson(),
Revision: item.GetRevision(),
PublishedVersion: item.GetPublishedVersion(),
CreatedByAdminID: item.GetCreatedByAdminId(),
UpdatedByAdminID: item.GetUpdatedByAdminId(),
PublishedByAdminID: item.GetPublishedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
PublishedAtMS: item.GetPublishedAtMs(),
ArchivedAtMS: item.GetArchivedAtMs(),
}
for _, locale := range item.GetLocales() {
out.Locales = append(out.Locales, domain.Locale{Locale: locale.GetLocale(), Title: locale.GetTitle(), Rules: locale.GetRules()})
}
for _, gift := range item.GetGifts() {
out.Gifts = append(out.Gifts, domain.Gift{
GiftID: gift.GetGiftId(), SortOrder: gift.GetSortOrder(), Name: gift.GetName(), IconURL: gift.GetIconUrl(),
CoinPrice: gift.GetCoinPrice(), PriceVersion: gift.GetPriceVersion(),
})
}
for _, task := range item.GetTasks() {
out.Tasks = append(out.Tasks, domain.Task{
TaskKey: task.GetTaskKey(), TaskType: task.GetTaskType(), TargetValue: task.GetTargetValue(),
RewardResourceGroupID: task.GetRewardResourceGroupId(), SortOrder: task.GetSortOrder(),
RewardSnapshot: activityTemplateRewardSnapshotFromProto(task.GetRewardSnapshotId(), task.GetRewardSnapshotHash(), task.GetRewardName(), task.GetRewardItems()),
})
}
for _, reward := range item.GetRankRewards() {
out.RankRewards = append(out.RankRewards, domain.RankReward{
BoardType: reward.GetBoardType(), RankFrom: reward.GetRankFrom(), RankTo: reward.GetRankTo(),
ResourceGroupID: reward.GetResourceGroupId(), SortOrder: reward.GetSortOrder(),
RewardSnapshot: activityTemplateRewardSnapshotFromProto(reward.GetRewardSnapshotId(), reward.GetRewardSnapshotHash(), reward.GetRewardName(), reward.GetRewardItems()),
})
}
for _, asset := range item.GetAssets() {
out.Assets = append(out.Assets, domain.Asset{
AssetKey: asset.GetAssetKey(), Locale: asset.GetLocale(), URL: asset.GetUrl(),
MediaType: asset.GetMediaType(), SortOrder: asset.GetSortOrder(),
})
}
return out
}
func activityTemplateToProto(item domain.Template) *activityv1.ActivityTemplate {
if item.TemplateID == "" && item.TemplateCode == "" && item.Name == "" {
return nil
}
out := &activityv1.ActivityTemplate{
AppCode: item.AppCode,
TemplateId: item.TemplateID,
TemplateCode: item.TemplateCode,
Name: item.Name,
ActivityType: item.ActivityType,
Status: item.Status,
LifecycleStatus: item.LifecycleStatus,
StartMs: item.StartMS,
EndMs: item.EndMS,
AllRegions: item.AllRegions,
RegionIds: append([]int64(nil), item.RegionIDs...),
Locales: make([]*activityv1.ActivityTemplateLocale, 0, len(item.Locales)),
Gifts: make([]*activityv1.ActivityTemplateGift, 0, len(item.Gifts)),
Tasks: make([]*activityv1.ActivityTemplateTask, 0, len(item.Tasks)),
DailyLeaderboardSize: item.DailyLeaderboardSize,
TotalLeaderboardSize: item.TotalLeaderboardSize,
RankRewards: make([]*activityv1.ActivityTemplateRankReward, 0, len(item.RankRewards)),
Assets: make([]*activityv1.ActivityTemplateAsset, 0, len(item.Assets)),
DisplayConfigJson: item.DisplayConfigJSON,
Revision: item.Revision,
PublishedVersion: item.PublishedVersion,
CreatedByAdminId: item.CreatedByAdminID,
UpdatedByAdminId: item.UpdatedByAdminID,
PublishedByAdminId: item.PublishedByAdminID,
CreatedAtMs: item.CreatedAtMS,
UpdatedAtMs: item.UpdatedAtMS,
PublishedAtMs: item.PublishedAtMS,
ArchivedAtMs: item.ArchivedAtMS,
}
for _, locale := range item.Locales {
out.Locales = append(out.Locales, &activityv1.ActivityTemplateLocale{Locale: locale.Locale, Title: locale.Title, Rules: locale.Rules})
}
for _, gift := range item.Gifts {
out.Gifts = append(out.Gifts, &activityv1.ActivityTemplateGift{
GiftId: gift.GiftID, SortOrder: gift.SortOrder, Name: gift.Name, IconUrl: gift.IconURL,
CoinPrice: gift.CoinPrice, PriceVersion: gift.PriceVersion,
})
}
for _, task := range item.Tasks {
out.Tasks = append(out.Tasks, &activityv1.ActivityTemplateTask{
TaskKey: task.TaskKey, TaskType: task.TaskType, TargetValue: task.TargetValue,
RewardResourceGroupId: task.RewardResourceGroupID, SortOrder: task.SortOrder,
RewardSnapshotId: task.RewardSnapshot.SnapshotID, RewardSnapshotHash: task.RewardSnapshot.SnapshotHash,
RewardName: task.RewardSnapshot.Name, RewardItems: activityTemplateRewardItemsToProto(task.RewardSnapshot.Items),
})
}
for _, reward := range item.RankRewards {
out.RankRewards = append(out.RankRewards, &activityv1.ActivityTemplateRankReward{
BoardType: reward.BoardType, RankFrom: reward.RankFrom, RankTo: reward.RankTo,
ResourceGroupId: reward.ResourceGroupID, SortOrder: reward.SortOrder,
RewardSnapshotId: reward.RewardSnapshot.SnapshotID, RewardSnapshotHash: reward.RewardSnapshot.SnapshotHash,
RewardName: reward.RewardSnapshot.Name, RewardItems: activityTemplateRewardItemsToProto(reward.RewardSnapshot.Items),
})
}
for _, asset := range item.Assets {
out.Assets = append(out.Assets, &activityv1.ActivityTemplateAsset{
AssetKey: asset.AssetKey, Locale: asset.Locale, Url: asset.URL, MediaType: asset.MediaType, SortOrder: asset.SortOrder,
})
}
return out
}
func activityTemplateRewardItemsToProto(items []domain.RewardItem) []*activityv1.ActivityTemplateRewardItem {
out := make([]*activityv1.ActivityTemplateRewardItem, 0, len(items))
for _, item := range items {
out = append(out, &activityv1.ActivityTemplateRewardItem{
ItemType: item.ItemType, ResourceId: item.ResourceID, ResourceType: item.ResourceType,
Name: item.Name, IconUrl: item.IconURL, Quantity: item.Quantity, DurationMs: item.DurationMS,
WalletAssetType: item.WalletAssetType, WalletAssetAmount: item.WalletAssetAmount, SortOrder: item.SortOrder,
})
}
return out
}
func activityTemplateRewardSnapshotFromProto(snapshotID, snapshotHash, name string, items []*activityv1.ActivityTemplateRewardItem) domain.RewardSnapshot {
out := domain.RewardSnapshot{SnapshotID: snapshotID, SnapshotHash: snapshotHash, Name: name, Items: make([]domain.RewardItem, 0, len(items))}
for _, item := range items {
out.Items = append(out.Items, domain.RewardItem{
ItemType: item.GetItemType(), ResourceID: item.GetResourceId(), ResourceType: item.GetResourceType(),
Name: item.GetName(), IconURL: item.GetIconUrl(), Quantity: item.GetQuantity(), DurationMS: item.GetDurationMs(),
WalletAssetType: item.GetWalletAssetType(), WalletAssetAmount: item.GetWalletAssetAmount(), SortOrder: item.GetSortOrder(),
})
}
return out
}
func activityTemplateSummaryToProto(item domain.Summary) *activityv1.ActivityTemplateSummary {
return &activityv1.ActivityTemplateSummary{
AppCode: item.AppCode, TemplateId: item.TemplateID, TemplateCode: item.TemplateCode, Name: item.Name,
ActivityType: item.ActivityType, Status: item.Status, LifecycleStatus: item.LifecycleStatus,
StartMs: item.StartMS, EndMs: item.EndMS, AllRegions: item.AllRegions, RegionIds: append([]int64(nil), item.RegionIDs...),
Revision: item.Revision, PublishedVersion: item.PublishedVersion,
CreatedByAdminId: item.CreatedByAdminID, UpdatedByAdminId: item.UpdatedByAdminID,
CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS,
}
}

View File

@ -7,6 +7,7 @@ import (
activityv1 "hyapp.local/api/proto/activity/v1" activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
"hyapp/pkg/xerr" "hyapp/pkg/xerr"
activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening" agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening"
broadcastservice "hyapp/services/activity-service/internal/service/broadcast" broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
growthservice "hyapp/services/activity-service/internal/service/growth" growthservice "hyapp/services/activity-service/internal/service/growth"
@ -27,19 +28,23 @@ type BroadcastServer struct {
roomReward *roomturnoverrewardservice.Service roomReward *roomturnoverrewardservice.Service
weeklyStar *weeklystarservice.Service weeklyStar *weeklystarservice.Service
agencyOpen *agencyopeningservice.Service agencyOpen *agencyopeningservice.Service
activityTemplate *activitytemplateservice.Service
} }
// NewBroadcastServer 创建播报和 room event 消费共用的 gRPC adapter。 // NewBroadcastServer 创建播报和 room event 消费共用的 gRPC adapter。
// room-service 的 outbox 事件只投递一次,但 activity-service 内部有多条独立投影: // room-service 的 outbox 事件只投递一次,但 activity-service 内部有多条独立投影:
// 播报负责 IM 展示growth 负责等级进度weekly-star 负责指定礼物榜单roomReward 负责房间流水。 // 播报负责 IM 展示growth 负责等级进度weekly-star 负责指定礼物榜单roomReward 负责房间流水。
// 这里把这些模块都挂到同一个 gRPC 入口,保证本地直连投递和 MQ 投递使用同一组业务副作用。 // 这里把这些模块都挂到同一个 gRPC 入口,保证本地直连投递和 MQ 投递使用同一组业务副作用。
func NewBroadcastServer(svc *broadcastservice.Service, growthSvc *growthservice.Service, weeklyStarSvc *weeklystarservice.Service, taskSvc *taskservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service) *BroadcastServer { func NewBroadcastServer(svc *broadcastservice.Service, growthSvc *growthservice.Service, weeklyStarSvc *weeklystarservice.Service, taskSvc *taskservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, activityTemplateSvc ...*activitytemplateservice.Service) *BroadcastServer {
server := &BroadcastServer{svc: svc} server := &BroadcastServer{svc: svc}
server.growth = growthSvc server.growth = growthSvc
server.weeklyStar = weeklyStarSvc server.weeklyStar = weeklyStarSvc
server.task = taskSvc server.task = taskSvc
server.roomReward = roomRewardSvc server.roomReward = roomRewardSvc
server.agencyOpen = agencyOpeningSvc server.agencyOpen = agencyOpeningSvc
if len(activityTemplateSvc) > 0 {
server.activityTemplate = activityTemplateSvc[0]
}
return server return server
} }
@ -130,6 +135,11 @@ func (s *BroadcastServer) ConsumeRoomEvent(ctx context.Context, req *activityv1.
return nil, xerr.ToGRPCError(err) return nil, xerr.ToGRPCError(err)
} }
} }
if s.activityTemplate != nil {
if _, err := s.activityTemplate.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
return nil, xerr.ToGRPCError(err)
}
}
if s.agencyOpen != nil { if s.agencyOpen != nil {
if _, err := s.agencyOpen.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil { if _, err := s.agencyOpen.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
return nil, xerr.ToGRPCError(err) return nil, xerr.ToGRPCError(err)

View File

@ -8,6 +8,7 @@ import (
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
"hyapp/pkg/xerr" "hyapp/pkg/xerr"
achievementservice "hyapp/services/activity-service/internal/service/achievement" achievementservice "hyapp/services/activity-service/internal/service/achievement"
activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening" agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening"
cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank" cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank"
growthservice "hyapp/services/activity-service/internal/service/growth" growthservice "hyapp/services/activity-service/internal/service/growth"
@ -27,10 +28,11 @@ type CronServer struct {
roomReward *roomturnoverrewardservice.Service roomReward *roomturnoverrewardservice.Service
agencyOpen *agencyopeningservice.Service agencyOpen *agencyopeningservice.Service
cpWeekly *cpweeklyrankservice.Service cpWeekly *cpweeklyrankservice.Service
activityTemplate *activitytemplateservice.Service
} }
// NewCronServer creates the internal cron adapter without exposing message storage. // NewCronServer creates the internal cron adapter without exposing message storage.
func NewCronServer(messageSvc *messageservice.Service, growthSvc *growthservice.Service, achievementSvc *achievementservice.Service, weeklyStarSvc *weeklystarservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, cpWeeklyRankSvc *cpweeklyrankservice.Service) *CronServer { func NewCronServer(messageSvc *messageservice.Service, growthSvc *growthservice.Service, achievementSvc *achievementservice.Service, weeklyStarSvc *weeklystarservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, cpWeeklyRankSvc *cpweeklyrankservice.Service, activityTemplateSvc ...*activitytemplateservice.Service) *CronServer {
server := &CronServer{message: messageSvc} server := &CronServer{message: messageSvc}
server.growth = growthSvc server.growth = growthSvc
server.achievement = achievementSvc server.achievement = achievementSvc
@ -38,9 +40,27 @@ func NewCronServer(messageSvc *messageservice.Service, growthSvc *growthservice.
server.roomReward = roomRewardSvc server.roomReward = roomRewardSvc
server.agencyOpen = agencyOpeningSvc server.agencyOpen = agencyOpeningSvc
server.cpWeekly = cpWeeklyRankSvc server.cpWeekly = cpWeeklyRankSvc
if len(activityTemplateSvc) > 0 {
server.activityTemplate = activityTemplateSvc[0]
}
return server return server
} }
// ProcessActivityTemplateSettlementBatch closes due UTC daily/total boards, freezes immutable
// snapshots and retries wallet resource-group grants with stable commands.
func (s *CronServer) ProcessActivityTemplateSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
if s.activityTemplate == nil {
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "activity template runtime service is not configured"))
}
claimed, processed, success, failure, hasMore, err := s.activityTemplate.ProcessRuntimeSettlementBatch(
ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs()))
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.CronBatchResponse{ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore}, nil
}
// ProcessMessageFanoutBatch claims and processes one message fanout page. // ProcessMessageFanoutBatch claims and processes one message fanout page.
func (s *CronServer) ProcessMessageFanoutBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { func (s *CronServer) ProcessMessageFanoutBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())

View File

@ -64,6 +64,13 @@ tasks:
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
app_codes: ["lalu","fami","huwaa"] app_codes: ["lalu","fami","huwaa"]
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
app_codes: ["lalu","fami","huwaa"]
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -64,6 +64,13 @@ tasks:
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
app_codes: ["lalu","fami","huwaa"] app_codes: ["lalu","fami","huwaa"]
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
app_codes: ["lalu","fami","huwaa"]
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -64,6 +64,13 @@ tasks:
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
app_codes: ["lalu","fami","huwaa"] app_codes: ["lalu","fami","huwaa"]
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
app_codes: ["lalu","fami","huwaa"]
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -127,6 +127,7 @@ func New(cfg config.Config) (*App, error) {
"temporary_growth_level": activityCron.ProcessTemporaryLevelBatch, "temporary_growth_level": activityCron.ProcessTemporaryLevelBatch,
"achievement_reward": activityCron.ProcessAchievementRewardBatch, "achievement_reward": activityCron.ProcessAchievementRewardBatch,
"weekly_star_settlement": activityCron.ProcessWeeklyStarSettlementBatch, "weekly_star_settlement": activityCron.ProcessWeeklyStarSettlementBatch,
"activity_template_settlement": activityCron.ProcessActivityTemplateSettlementBatch,
"agency_opening_settlement": activityCron.ProcessAgencyOpeningSettlementBatch, "agency_opening_settlement": activityCron.ProcessAgencyOpeningSettlementBatch,
"cp_weekly_rank_settlement": activityCron.ProcessCPWeeklyRankSettlementBatch, "cp_weekly_rank_settlement": activityCron.ProcessCPWeeklyRankSettlementBatch,
"room_turnover_reward_settlement": activityCron.ProcessRoomTurnoverRewardSettlementBatch, "room_turnover_reward_settlement": activityCron.ProcessRoomTurnoverRewardSettlementBatch,

View File

@ -192,6 +192,14 @@ func defaultTasks() map[string]TaskConfig {
BatchSize: 50, BatchSize: 50,
AppCodes: []string{defaultAppCode}, AppCodes: []string{defaultAppCode},
}, },
"activity_template_settlement": {
Enabled: true,
Interval: "5s",
Timeout: "30s",
LockTTL: "1m",
BatchSize: 50,
AppCodes: []string{defaultAppCode},
},
"agency_opening_settlement": { "agency_opening_settlement": {
Enabled: true, Enabled: true,
Interval: "5s", Interval: "5s",

View File

@ -63,6 +63,15 @@ func (c *ActivityCronClient) ProcessWeeklyStarSettlementBatch(ctx context.Contex
return activityCronBatchResult(resp), nil return activityCronBatchResult(resp), nil
} }
// ProcessActivityTemplateSettlementBatch handles immutable-version daily and total leaderboard rewards.
func (c *ActivityCronClient) ProcessActivityTemplateSettlementBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) {
resp, err := c.client.ProcessActivityTemplateSettlementBatch(ctx, activityCronBatchRequest(req))
if err != nil {
return scheduler.BatchResult{}, err
}
return activityCronBatchResult(resp), nil
}
// ProcessAgencyOpeningSettlementBatch handles ended agency-opening leaderboard rewards. // ProcessAgencyOpeningSettlementBatch handles ended agency-opening leaderboard rewards.
func (c *ActivityCronClient) ProcessAgencyOpeningSettlementBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) { func (c *ActivityCronClient) ProcessAgencyOpeningSettlementBatch(ctx context.Context, req scheduler.BatchRequest) (scheduler.BatchResult, error) {
resp, err := c.client.ProcessAgencyOpeningSettlementBatch(ctx, activityCronBatchRequest(req)) resp, err := c.client.ProcessAgencyOpeningSettlementBatch(ctx, activityCronBatchRequest(req))

View File

@ -125,6 +125,7 @@ func New(cfg config.Config) (*App, error) {
var weeklyStarClient client.WeeklyStarClient = client.NewGRPCWeeklyStarClient(activityConn) var weeklyStarClient client.WeeklyStarClient = client.NewGRPCWeeklyStarClient(activityConn)
var cpWeeklyRankClient client.CPWeeklyRankClient = client.NewGRPCCPWeeklyRankClient(activityConn) var cpWeeklyRankClient client.CPWeeklyRankClient = client.NewGRPCCPWeeklyRankClient(activityConn)
var agencyOpeningClient client.AgencyOpeningClient = client.NewGRPCAgencyOpeningClient(activityConn) var agencyOpeningClient client.AgencyOpeningClient = client.NewGRPCAgencyOpeningClient(activityConn)
var activityTemplateRuntimeClient client.ActivityTemplateRuntimeClient = client.NewGRPCActivityTemplateRuntimeClient(activityConn)
var wheelClient client.WheelClient = client.NewGRPCWheelClient(activityConn) var wheelClient client.WheelClient = client.NewGRPCWheelClient(activityConn)
var broadcastClient client.BroadcastClient = client.NewGRPCBroadcastClient(activityConn) var broadcastClient client.BroadcastClient = client.NewGRPCBroadcastClient(activityConn)
var gameClient client.GameClient = client.NewGRPCGameClient(gameConn) var gameClient client.GameClient = client.NewGRPCGameClient(gameConn)
@ -228,6 +229,7 @@ func New(cfg config.Config) (*App, error) {
handler.SetWeeklyStarClient(weeklyStarClient) handler.SetWeeklyStarClient(weeklyStarClient)
handler.SetCPWeeklyRankClient(cpWeeklyRankClient) handler.SetCPWeeklyRankClient(cpWeeklyRankClient)
handler.SetAgencyOpeningClient(agencyOpeningClient) handler.SetAgencyOpeningClient(agencyOpeningClient)
handler.SetActivityTemplateRuntimeClient(activityTemplateRuntimeClient)
handler.SetWheelClient(wheelClient) handler.SetWheelClient(wheelClient)
handler.SetBroadcastClient(broadcastClient) handler.SetBroadcastClient(broadcastClient)
handler.SetGameClient(gameClient) handler.SetGameClient(gameClient)

View File

@ -0,0 +1,46 @@
package client
import (
"context"
"google.golang.org/grpc"
activityv1 "hyapp.local/api/proto/activity/v1"
)
// ActivityTemplateRuntimeClient 是 gateway 对活动模版 App 运行态的唯一内部依赖。
// gateway 只负责鉴权身份、可信区域和 HTTP 协议转换版本命中、UTC 日任务、领奖幂等和榜单事实都由 activity-service 决定。
type ActivityTemplateRuntimeClient interface {
GetCurrentActivityTemplate(ctx context.Context, req *activityv1.GetCurrentActivityTemplateRequest) (*activityv1.GetCurrentActivityTemplateResponse, error)
VisitActivityTemplate(ctx context.Context, req *activityv1.VisitActivityTemplateRequest) (*activityv1.VisitActivityTemplateResponse, error)
ListActivityTemplateTasks(ctx context.Context, req *activityv1.ListActivityTemplateTasksRequest) (*activityv1.ListActivityTemplateTasksResponse, error)
ClaimActivityTemplateTaskReward(ctx context.Context, req *activityv1.ClaimActivityTemplateTaskRewardRequest) (*activityv1.ClaimActivityTemplateTaskRewardResponse, error)
ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.ListActivityTemplateLeaderboardRequest) (*activityv1.ListActivityTemplateLeaderboardResponse, error)
}
type grpcActivityTemplateRuntimeClient struct {
client activityv1.ActivityTemplateRuntimeServiceClient
}
func NewGRPCActivityTemplateRuntimeClient(conn *grpc.ClientConn) ActivityTemplateRuntimeClient {
return &grpcActivityTemplateRuntimeClient{client: activityv1.NewActivityTemplateRuntimeServiceClient(conn)}
}
func (c *grpcActivityTemplateRuntimeClient) GetCurrentActivityTemplate(ctx context.Context, req *activityv1.GetCurrentActivityTemplateRequest) (*activityv1.GetCurrentActivityTemplateResponse, error) {
return c.client.GetCurrentActivityTemplate(ctx, req)
}
func (c *grpcActivityTemplateRuntimeClient) VisitActivityTemplate(ctx context.Context, req *activityv1.VisitActivityTemplateRequest) (*activityv1.VisitActivityTemplateResponse, error) {
return c.client.VisitActivityTemplate(ctx, req)
}
func (c *grpcActivityTemplateRuntimeClient) ListActivityTemplateTasks(ctx context.Context, req *activityv1.ListActivityTemplateTasksRequest) (*activityv1.ListActivityTemplateTasksResponse, error) {
return c.client.ListActivityTemplateTasks(ctx, req)
}
func (c *grpcActivityTemplateRuntimeClient) ClaimActivityTemplateTaskReward(ctx context.Context, req *activityv1.ClaimActivityTemplateTaskRewardRequest) (*activityv1.ClaimActivityTemplateTaskRewardResponse, error) {
return c.client.ClaimActivityTemplateTaskReward(ctx, req)
}
func (c *grpcActivityTemplateRuntimeClient) ListActivityTemplateLeaderboard(ctx context.Context, req *activityv1.ListActivityTemplateLeaderboardRequest) (*activityv1.ListActivityTemplateLeaderboardResponse, error) {
return c.client.ListActivityTemplateLeaderboard(ctx, req)
}

View File

@ -0,0 +1,780 @@
package activityapi
import (
"encoding/json"
"math"
"net/http"
"strconv"
"strings"
"time"
"golang.org/x/text/language"
activityv1 "hyapp.local/api/proto/activity/v1"
userv1 "hyapp.local/api/proto/user/v1"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
)
const activityTemplateUTCDayLayout = "2006-01-02"
type activityTemplateLocaleData struct {
Locale string `json:"locale"`
Title string `json:"title"`
Rules string `json:"rules"`
}
type activityTemplateGiftData struct {
GiftID string `json:"gift_id"`
SortOrder int32 `json:"sort_order"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
CoinPrice int64 `json:"coin_price"`
PriceVersion string `json:"price_version"`
}
type activityTemplateRewardItemData struct {
ItemType string `json:"item_type"`
ResourceID int64 `json:"resource_id"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
IconURL string `json:"icon_url"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
WalletAssetType string `json:"wallet_asset_type"`
WalletAssetAmount int64 `json:"wallet_asset_amount"`
SortOrder int32 `json:"sort_order"`
}
type activityTemplateTaskDefinitionData struct {
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TargetValue int64 `json:"target_value"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardName string `json:"reward_name"`
RewardItems []activityTemplateRewardItemData `json:"reward_items"`
}
type activityTemplateRankRewardData struct {
BoardType string `json:"board_type"`
RankFrom int32 `json:"rank_from"`
RankTo int32 `json:"rank_to"`
ResourceGroupID int64 `json:"resource_group_id"`
SortOrder int32 `json:"sort_order"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardName string `json:"reward_name"`
RewardItems []activityTemplateRewardItemData `json:"reward_items"`
}
type activityTemplateAssetData struct {
AssetKey string `json:"asset_key"`
Locale string `json:"locale"`
URL string `json:"url"`
MediaType string `json:"media_type"`
SortOrder int32 `json:"sort_order"`
}
// activityTemplateRuntimeData is deliberately smaller than the admin aggregate. App clients receive
// only the immutable published presentation/rule snapshot and runtime window, never admin IDs,
// optimistic revisions or mutable draft metadata.
type activityTemplateRuntimeData struct {
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
ActivityType string `json:"activity_type"`
Status string `json:"status"`
LifecycleStatus string `json:"lifecycle_status"`
StartMS int64 `json:"start_ms"`
EndMS int64 `json:"end_ms"`
AllRegions bool `json:"all_regions"`
RegionIDs []int64 `json:"region_ids"`
Locales []activityTemplateLocaleData `json:"locales"`
Locale *activityTemplateLocaleData `json:"locale,omitempty"`
Gifts []activityTemplateGiftData `json:"gifts"`
Tasks []activityTemplateTaskDefinitionData `json:"tasks"`
DailyLeaderboardSize int32 `json:"daily_leaderboard_size"`
TotalLeaderboardSize int32 `json:"total_leaderboard_size"`
RankRewards []activityTemplateRankRewardData `json:"rank_rewards"`
Assets []activityTemplateAssetData `json:"assets"`
DisplayConfig json.RawMessage `json:"display_config"`
VersionNo int64 `json:"version_no"`
RuntimeFromMS int64 `json:"runtime_from_ms"`
RuntimeToMS int64 `json:"runtime_to_ms"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type activityTemplateRuntimeTaskData struct {
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
VersionNo int64 `json:"version_no"`
TaskDay string `json:"task_day"`
TaskKey string `json:"task_key"`
TaskType string `json:"task_type"`
TargetValue int64 `json:"target_value"`
ProgressValue int64 `json:"progress_value"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
Status string `json:"status"`
Claimable bool `json:"claimable"`
CompletedAtMS int64 `json:"completed_at_ms"`
ClaimedAtMS int64 `json:"claimed_at_ms"`
SortOrder int32 `json:"sort_order"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardName string `json:"reward_name"`
RewardItems []activityTemplateRewardItemData `json:"reward_items"`
}
type activityTemplateTaskClaimData struct {
ClaimID string `json:"claim_id"`
TemplateID string `json:"template_id"`
TemplateCode string `json:"template_code"`
VersionNo int64 `json:"version_no"`
TaskDay string `json:"task_day"`
TaskKey string `json:"task_key"`
UserID string `json:"user_id"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
Status string `json:"status"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
GrantedAtMS int64 `json:"granted_at_ms"`
RewardSnapshotID string `json:"reward_snapshot_id"`
RewardName string `json:"reward_name"`
RewardItems []activityTemplateRewardItemData `json:"reward_items"`
}
type activityTemplateLeaderboardUserData struct {
UserID string `json:"user_id"`
DisplayUserID string `json:"display_user_id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
}
type activityTemplateLeaderboardEntryData struct {
RankNo int32 `json:"rank_no"`
UserID string `json:"user_id"`
Score int64 `json:"score"`
GiftCount int64 `json:"gift_count"`
CoinAmount int64 `json:"coin_amount"`
FirstScoredAtMS int64 `json:"first_scored_at_ms"`
Snapshot bool `json:"snapshot"`
User *activityTemplateLeaderboardUserData `json:"user,omitempty"`
}
type activityTemplateVisitBody struct {
CommandID string `json:"command_id"`
VersionNo int64 `json:"version_no"`
}
type activityTemplateTaskClaimBody struct {
TaskDay string `json:"task_day"`
CommandID string `json:"command_id"`
VersionNo int64 `json:"version_no"`
}
type activityTemplateCurrentData struct {
Found bool `json:"found"`
Runtime *activityTemplateRuntimeData `json:"runtime,omitempty"`
}
type activityTemplateVisitData struct {
Runtime *activityTemplateRuntimeData `json:"runtime,omitempty"`
Tasks []activityTemplateRuntimeTaskData `json:"tasks"`
FirstVisit bool `json:"first_visit"`
}
type activityTemplateTasksData struct {
Runtime *activityTemplateRuntimeData `json:"runtime,omitempty"`
Tasks []activityTemplateRuntimeTaskData `json:"tasks"`
NextRefreshAtMS int64 `json:"next_refresh_at_ms"`
}
type activityTemplateClaimData struct {
Claim *activityTemplateTaskClaimData `json:"claim,omitempty"`
Claimed bool `json:"claimed"`
}
type activityTemplateLeaderboardData struct {
Runtime *activityTemplateRuntimeData `json:"runtime,omitempty"`
BoardType string `json:"board_type"`
PeriodKey string `json:"period_key"`
PeriodStartMS int64 `json:"period_start_ms"`
PeriodEndMS int64 `json:"period_end_ms"`
SettleAfterMS int64 `json:"settle_after_ms"`
PeriodStatus string `json:"period_status"`
Entries []activityTemplateLeaderboardEntryData `json:"entries"`
MyEntry *activityTemplateLeaderboardEntryData `json:"my_entry,omitempty"`
Total int64 `json:"total"`
Page int32 `json:"page"`
PageSize int32 `json:"page_size"`
}
// getCurrentActivityTemplate resolves region from the authenticated user's current profile. A client
// cannot probe another region by crafting a query parameter, which keeps rollout targeting identical
// between page configuration, task progress and gift-event attribution.
func (h *Handler) getCurrentActivityTemplate(writer http.ResponseWriter, request *http.Request) {
if h.activityTemplateRuntime == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
userID := auth.UserIDFromContext(request.Context())
regionID, ok := h.resolveActivityTemplateRegionID(writer, request, userID)
if !ok {
return
}
locale := activityTemplateRequestLocale(request)
resp, err := h.activityTemplateRuntime.GetCurrentActivityTemplate(request.Context(), &activityv1.GetCurrentActivityTemplateRequest{
Meta: httpkit.ActivityMeta(request), UserId: userID, RegionId: regionID, Locale: locale,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
data := activityTemplateCurrentData{Found: resp.GetFound()}
if resp.GetRuntime() != nil {
runtime := activityTemplateRuntimeFromProto(resp.GetRuntime(), locale)
data.Runtime = &runtime
}
httpkit.WriteOK(writer, request, data)
}
// visitActivityTemplate records the explicit daily_visit fact. request_id remains tracing metadata;
// only the client-provided command_id is allowed to define replay identity across HTTP retries.
func (h *Handler) visitActivityTemplate(writer http.ResponseWriter, request *http.Request) {
if h.activityTemplateRuntime == nil || h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
templateCode := strings.TrimSpace(request.PathValue("template_code"))
var body activityTemplateVisitBody
if !httpkit.Decode(writer, request, &body) {
return
}
body.CommandID = strings.TrimSpace(body.CommandID)
if templateCode == "" || body.CommandID == "" || body.VersionNo <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
regionID, ok := h.resolveActivityTemplateRegionID(writer, request, userID)
if !ok {
return
}
resp, err := h.activityTemplateRuntime.VisitActivityTemplate(request.Context(), &activityv1.VisitActivityTemplateRequest{
Meta: httpkit.ActivityMeta(request), UserId: userID, RegionId: regionID, TemplateCode: templateCode,
CommandId: body.CommandID, VersionNo: body.VersionNo,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, activityTemplateVisitData{
Runtime: activityTemplateRuntimePointerFromProto(resp.GetRuntime(), activityTemplateRequestLocale(request)),
Tasks: activityTemplateRuntimeTasksFromProto(resp.GetTasks()), FirstVisit: resp.GetFirstVisit(),
})
}
func (h *Handler) listActivityTemplateTasks(writer http.ResponseWriter, request *http.Request) {
if h.activityTemplateRuntime == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
templateCode := strings.TrimSpace(request.PathValue("template_code"))
taskDay, ok := normalizeOptionalActivityTemplateUTCDay(request.URL.Query().Get("task_day"))
versionNo, versionOK := optionalActivityTemplateVersionQuery(request)
if templateCode == "" || !ok || !versionOK {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
regionID := int64(0)
if versionNo == 0 {
var regionOK bool
regionID, regionOK = h.resolveActivityTemplateRegionID(writer, request, userID)
if !regionOK {
return
}
}
resp, err := h.activityTemplateRuntime.ListActivityTemplateTasks(request.Context(), &activityv1.ListActivityTemplateTasksRequest{
Meta: httpkit.ActivityMeta(request), UserId: userID, RegionId: regionID, TemplateCode: templateCode,
TaskDay: taskDay, VersionNo: versionNo,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, activityTemplateTasksData{
Runtime: activityTemplateRuntimePointerFromProto(resp.GetRuntime(), activityTemplateRequestLocale(request)),
Tasks: activityTemplateRuntimeTasksFromProto(resp.GetTasks()), NextRefreshAtMS: resp.GetNextRefreshAtMs(),
})
}
func (h *Handler) claimActivityTemplateTask(writer http.ResponseWriter, request *http.Request) {
if h.activityTemplateRuntime == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
templateCode := strings.TrimSpace(request.PathValue("template_code"))
taskKey := strings.TrimSpace(request.PathValue("task_key"))
var body activityTemplateTaskClaimBody
if !httpkit.Decode(writer, request, &body) {
return
}
taskDay, validDay := normalizeRequiredActivityTemplateUTCDay(body.TaskDay)
body.CommandID = strings.TrimSpace(body.CommandID)
if templateCode == "" || taskKey == "" || !validDay || body.CommandID == "" || body.VersionNo <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
resp, err := h.activityTemplateRuntime.ClaimActivityTemplateTaskReward(request.Context(), &activityv1.ClaimActivityTemplateTaskRewardRequest{
Meta: httpkit.ActivityMeta(request), UserId: userID, RegionId: 0, TemplateCode: templateCode,
TaskDay: taskDay, TaskKey: taskKey, CommandId: body.CommandID, VersionNo: body.VersionNo,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, activityTemplateClaimData{
Claim: activityTemplateTaskClaimPointerFromProto(resp.GetClaim()), Claimed: resp.GetClaimed(),
})
}
func (h *Handler) listActivityTemplateDailyLeaderboard(writer http.ResponseWriter, request *http.Request) {
h.listActivityTemplateLeaderboard(writer, request, "daily")
}
func (h *Handler) listActivityTemplateTotalLeaderboard(writer http.ResponseWriter, request *http.Request) {
h.listActivityTemplateLeaderboard(writer, request, "total")
}
func (h *Handler) listActivityTemplateLeaderboard(writer http.ResponseWriter, request *http.Request, boardType string) {
if h.activityTemplateRuntime == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
templateCode := strings.TrimSpace(request.PathValue("template_code"))
page, pageOK := httpkit.PositiveInt32Query(request, "page", 1)
pageSize, pageSizeOK := httpkit.PositiveInt32Query(request, "page_size", 50)
versionNo, versionOK := optionalActivityTemplateVersionQuery(request)
if pageSize > 100 {
pageSize = 100
}
periodKey := "total"
periodOK := true
if boardType == "daily" {
periodKey, periodOK = normalizeOptionalActivityTemplateUTCDay(request.URL.Query().Get("period_key"))
}
if templateCode == "" || !pageOK || !pageSizeOK || !periodOK || !versionOK {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
userID := auth.UserIDFromContext(request.Context())
regionID := int64(0)
if versionNo == 0 {
var regionOK bool
regionID, regionOK = h.resolveActivityTemplateRegionID(writer, request, userID)
if !regionOK {
return
}
}
resp, err := h.activityTemplateRuntime.ListActivityTemplateLeaderboard(request.Context(), &activityv1.ListActivityTemplateLeaderboardRequest{
Meta: httpkit.ActivityMeta(request), UserId: userID, RegionId: regionID, TemplateCode: templateCode,
BoardType: boardType, PeriodKey: periodKey, Page: page, PageSize: pageSize, VersionNo: versionNo,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
ids := activityTemplateLeaderboardUserIDs(resp.GetEntries(), resp.GetMyEntry())
users := h.resolveActivityTemplateLeaderboardUsers(request, ids)
data := activityTemplateLeaderboardData{
Runtime: activityTemplateRuntimePointerFromProto(resp.GetRuntime(), activityTemplateRequestLocale(request)),
BoardType: resp.GetBoardType(), PeriodKey: resp.GetPeriodKey(), PeriodStartMS: resp.GetPeriodStartMs(),
PeriodEndMS: resp.GetPeriodEndMs(), SettleAfterMS: resp.GetSettleAfterMs(), PeriodStatus: resp.GetPeriodStatus(),
Entries: activityTemplateLeaderboardEntriesFromProto(resp.GetEntries(), users),
Total: resp.GetTotal(), Page: page, PageSize: pageSize,
}
if resp.GetMyEntry() != nil {
entry := activityTemplateLeaderboardEntryFromProto(resp.GetMyEntry(), users)
data.MyEntry = &entry
}
httpkit.WriteOK(writer, request, data)
}
func optionalActivityTemplateVersionQuery(request *http.Request) (int64, bool) {
raw := strings.TrimSpace(request.URL.Query().Get("version_no"))
if raw == "" {
return 0, true
}
versionNo, err := strconv.ParseInt(raw, 10, 64)
return versionNo, err == nil && versionNo > 0
}
func (h *Handler) resolveActivityTemplateRegionID(writer http.ResponseWriter, request *http.Request, userID int64) (int64, bool) {
if h.userProfileClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return 0, false
}
resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""), UserId: userID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return 0, false
}
regionID := resp.GetUser().GetRegionId()
if regionID <= 0 {
// A completed profile without a canonical region is inconsistent upstream state. Do not accept a
// caller supplied fallback, because that would bypass regional publication targeting.
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return 0, false
}
return regionID, true
}
func (h *Handler) resolveActivityTemplateLeaderboardUsers(request *http.Request, ids []int64) map[int64]activityTemplateLeaderboardUserData {
users := make(map[int64]activityTemplateLeaderboardUserData)
ids = uniquePositiveIDs(ids)
if len(ids) == 0 || h.userProfileClient == nil {
return users
}
resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{
Meta: httpkit.UserMeta(request, ""), UserIds: ids,
})
if err != nil {
// Ranking score/order are owned by activity-service. Profile decoration is best effort so a
// transient user-service read failure cannot make an otherwise valid ranking page unavailable.
return users
}
for _, item := range resp.GetUsers() {
users[item.GetUserId()] = activityTemplateLeaderboardUserData{
UserID: strconv.FormatInt(item.GetUserId(), 10), DisplayUserID: item.GetDisplayUserId(),
Username: item.GetUsername(), Avatar: item.GetAvatar(),
}
}
return users
}
func activityTemplateRuntimeFromProto(runtime *activityv1.ActivityTemplateRuntime, requestedLocale string) activityTemplateRuntimeData {
data := activityTemplateRuntimeData{
RegionIDs: []int64{}, Locales: []activityTemplateLocaleData{}, Gifts: []activityTemplateGiftData{},
Tasks: []activityTemplateTaskDefinitionData{}, RankRewards: []activityTemplateRankRewardData{},
Assets: []activityTemplateAssetData{}, DisplayConfig: json.RawMessage(`{}`),
}
if runtime == nil || runtime.GetTemplate() == nil {
return data
}
template := runtime.GetTemplate()
data.TemplateID = template.GetTemplateId()
data.TemplateCode = template.GetTemplateCode()
data.ActivityType = template.GetActivityType()
data.Status = template.GetStatus()
data.LifecycleStatus = template.GetLifecycleStatus()
data.StartMS = template.GetStartMs()
data.EndMS = template.GetEndMs()
data.AllRegions = template.GetAllRegions()
data.RegionIDs = append(data.RegionIDs, template.GetRegionIds()...)
seenLocales := make(map[string]struct{}, len(template.GetLocales()))
for _, item := range template.GetLocales() {
locale, valid := canonicalActivityTemplateLanguageTag(item.GetLocale())
if !valid {
// 发布写路径会校验 locale读取侧仍需失败关闭避免旧数据或跨版本脏数据把非法 tag 暴露给 App。
continue
}
key := strings.ToLower(locale)
if _, exists := seenLocales[key]; exists {
continue
}
seenLocales[key] = struct{}{}
data.Locales = append(data.Locales, activityTemplateLocaleData{Locale: locale, Title: item.GetTitle(), Rules: item.GetRules()})
}
if selected, ok := selectActivityTemplateLocale(data.Locales, requestedLocale); ok {
data.Locale = &selected
}
for _, item := range template.GetGifts() {
data.Gifts = append(data.Gifts, activityTemplateGiftData{
GiftID: item.GetGiftId(), SortOrder: item.GetSortOrder(), Name: item.GetName(), IconURL: item.GetIconUrl(),
CoinPrice: item.GetCoinPrice(), PriceVersion: item.GetPriceVersion(),
})
}
for _, item := range template.GetTasks() {
data.Tasks = append(data.Tasks, activityTemplateTaskDefinitionData{
TaskKey: item.GetTaskKey(), TaskType: item.GetTaskType(), TargetValue: item.GetTargetValue(),
RewardResourceGroupID: item.GetRewardResourceGroupId(), SortOrder: item.GetSortOrder(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardName: item.GetRewardName(),
RewardItems: activityTemplateRewardItemsFromProto(item.GetRewardItems()),
})
}
data.DailyLeaderboardSize = template.GetDailyLeaderboardSize()
data.TotalLeaderboardSize = template.GetTotalLeaderboardSize()
for _, item := range template.GetRankRewards() {
data.RankRewards = append(data.RankRewards, activityTemplateRankRewardData{
BoardType: item.GetBoardType(), RankFrom: item.GetRankFrom(), RankTo: item.GetRankTo(),
ResourceGroupID: item.GetResourceGroupId(), SortOrder: item.GetSortOrder(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardName: item.GetRewardName(),
RewardItems: activityTemplateRewardItemsFromProto(item.GetRewardItems()),
})
}
for _, item := range template.GetAssets() {
assetLocale := strings.TrimSpace(item.GetLocale())
if assetLocale != "*" {
var valid bool
assetLocale, valid = canonicalActivityTemplateLanguageTag(assetLocale)
if !valid {
continue
}
}
data.Assets = append(data.Assets, activityTemplateAssetData{
AssetKey: item.GetAssetKey(), Locale: assetLocale, URL: item.GetUrl(), MediaType: item.GetMediaType(), SortOrder: item.GetSortOrder(),
})
}
if raw := []byte(strings.TrimSpace(template.GetDisplayConfigJson())); len(raw) > 0 && json.Valid(raw) {
data.DisplayConfig = append(json.RawMessage(nil), raw...)
}
data.VersionNo = runtime.GetVersionNo()
data.RuntimeFromMS = runtime.GetRuntimeFromMs()
data.RuntimeToMS = runtime.GetRuntimeToMs()
data.ServerTimeMS = runtime.GetServerTimeMs()
return data
}
func activityTemplateRuntimePointerFromProto(runtime *activityv1.ActivityTemplateRuntime, requestedLocale string) *activityTemplateRuntimeData {
if runtime == nil {
return nil
}
data := activityTemplateRuntimeFromProto(runtime, requestedLocale)
return &data
}
func activityTemplateRuntimeTasksFromProto(items []*activityv1.ActivityTemplateRuntimeTask) []activityTemplateRuntimeTaskData {
result := make([]activityTemplateRuntimeTaskData, 0, len(items))
for _, item := range items {
result = append(result, activityTemplateRuntimeTaskData{
TemplateID: item.GetTemplateId(), TemplateCode: item.GetTemplateCode(), VersionNo: item.GetVersionNo(),
TaskDay: item.GetTaskDay(), TaskKey: item.GetTaskKey(), TaskType: item.GetTaskType(), TargetValue: item.GetTargetValue(),
ProgressValue: item.GetProgressValue(), RewardResourceGroupID: item.GetRewardResourceGroupId(),
Status: item.GetStatus(), Claimable: item.GetClaimable(), CompletedAtMS: item.GetCompletedAtMs(),
ClaimedAtMS: item.GetClaimedAtMs(), SortOrder: item.GetSortOrder(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardName: item.GetRewardName(),
RewardItems: activityTemplateRewardItemsFromProto(item.GetRewardItems()),
})
}
return result
}
func activityTemplateTaskClaimPointerFromProto(item *activityv1.ActivityTemplateTaskClaim) *activityTemplateTaskClaimData {
if item == nil {
return nil
}
return &activityTemplateTaskClaimData{
ClaimID: item.GetClaimId(), TemplateID: item.GetTemplateId(),
TemplateCode: item.GetTemplateCode(), VersionNo: item.GetVersionNo(), TaskDay: item.GetTaskDay(),
TaskKey: item.GetTaskKey(), UserID: strconv.FormatInt(item.GetUserId(), 10),
RewardResourceGroupID: item.GetRewardResourceGroupId(), Status: item.GetStatus(),
CreatedAtMS: item.GetCreatedAtMs(), UpdatedAtMS: item.GetUpdatedAtMs(), GrantedAtMS: item.GetGrantedAtMs(),
RewardSnapshotID: item.GetRewardSnapshotId(), RewardName: item.GetRewardName(),
RewardItems: activityTemplateRewardItemsFromProto(item.GetRewardItems()),
}
}
func activityTemplateRewardItemsFromProto(items []*activityv1.ActivityTemplateRewardItem) []activityTemplateRewardItemData {
out := make([]activityTemplateRewardItemData, 0, len(items))
for _, item := range items {
out = append(out, activityTemplateRewardItemData{
ItemType: item.GetItemType(), ResourceID: item.GetResourceId(), ResourceType: item.GetResourceType(),
Name: item.GetName(), IconURL: item.GetIconUrl(), Quantity: item.GetQuantity(), DurationMS: item.GetDurationMs(),
WalletAssetType: item.GetWalletAssetType(), WalletAssetAmount: item.GetWalletAssetAmount(), SortOrder: item.GetSortOrder(),
})
}
return out
}
func activityTemplateLeaderboardEntriesFromProto(items []*activityv1.ActivityTemplateLeaderboardEntry, users map[int64]activityTemplateLeaderboardUserData) []activityTemplateLeaderboardEntryData {
result := make([]activityTemplateLeaderboardEntryData, 0, len(items))
for _, item := range items {
result = append(result, activityTemplateLeaderboardEntryFromProto(item, users))
}
return result
}
func activityTemplateLeaderboardEntryFromProto(item *activityv1.ActivityTemplateLeaderboardEntry, users map[int64]activityTemplateLeaderboardUserData) activityTemplateLeaderboardEntryData {
data := activityTemplateLeaderboardEntryData{}
if item == nil {
return data
}
data.RankNo = item.GetRankNo()
data.UserID = strconv.FormatInt(item.GetUserId(), 10)
data.Score = item.GetScore()
data.GiftCount = item.GetGiftCount()
data.CoinAmount = item.GetCoinAmount()
data.FirstScoredAtMS = item.GetFirstScoredAtMs()
data.Snapshot = item.GetSnapshot()
if user, ok := users[item.GetUserId()]; ok {
data.User = &user
}
return data
}
func activityTemplateLeaderboardUserIDs(items []*activityv1.ActivityTemplateLeaderboardEntry, myEntry *activityv1.ActivityTemplateLeaderboardEntry) []int64 {
ids := make([]int64, 0, len(items)+1)
for _, item := range items {
ids = append(ids, item.GetUserId())
}
if myEntry != nil {
ids = append(ids, myEntry.GetUserId())
}
return ids
}
func normalizeOptionalActivityTemplateUTCDay(raw string) (string, bool) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", true
}
return normalizeRequiredActivityTemplateUTCDay(raw)
}
func normalizeRequiredActivityTemplateUTCDay(raw string) (string, bool) {
raw = strings.TrimSpace(raw)
parsed, err := time.Parse(activityTemplateUTCDayLayout, raw)
if err != nil || parsed.Location() != time.UTC || parsed.Format(activityTemplateUTCDayLayout) != raw {
return "", false
}
return raw, true
}
func activityTemplateRequestLocale(request *http.Request) string {
// 显式 query 优先于客户端语言头;每一层只有合法 BCP-47 才能截断回退链,非法输入不会原样进入 gRPC 或响应。
if locale, valid := canonicalActivityTemplateLanguageTag(request.URL.Query().Get("locale")); valid {
return locale
}
if locale, valid := canonicalActivityTemplateLanguageTag(request.Header.Get("X-App-Language")); valid {
return locale
}
// X-Language 是旧客户端兼容入口,优先级低于规范的 X-App-Language但仍高于浏览器协商头。
if locale, valid := canonicalActivityTemplateLanguageTag(request.Header.Get("X-Language")); valid {
return locale
}
if locale, valid := preferredActivityTemplateAcceptLanguage(request.Header.Get("Accept-Language")); valid {
return locale
}
return "en"
}
func selectActivityTemplateLocale(locales []activityTemplateLocaleData, requested string) (activityTemplateLocaleData, bool) {
requested, valid := canonicalActivityTemplateLanguageTag(requested)
if !valid {
requested = "en"
}
// exact 保留地区、脚本和扩展差异,例如 zh-CN 与 zh-TW、pt-BR 与 pt-PT 不能提前折叠为基础语言。
for _, item := range locales {
if strings.EqualFold(item.Locale, requested) {
return item, true
}
}
requestedBase := activityTemplateLanguageBase(requested)
if requestedBase != "" {
// 优先使用运营明确配置的 language-only 文案,再选择同语言的第一个候选;配置顺序即稳定决胜顺序。
for _, item := range locales {
if strings.EqualFold(item.Locale, requestedBase) {
return item, true
}
}
for _, item := range locales {
if activityTemplateLanguageBase(item.Locale) == requestedBase {
return item, true
}
}
}
for _, item := range locales {
if strings.EqualFold(strings.TrimSpace(item.Locale), "en") {
return item, true
}
}
for _, item := range locales {
if activityTemplateLanguageBase(item.Locale) == "en" {
return item, true
}
}
if len(locales) > 0 {
return locales[0], true
}
return activityTemplateLocaleData{}, false
}
// canonicalActivityTemplateLanguageTag 在 language.Parse 前做 ASCII/分段约束。x/text 会为兼容 POSIX
// 接受下划线并尝试修复HTTP 边界不能把这类非 BCP-47 输入静默改写后继续暴露。
func canonicalActivityTemplateLanguageTag(raw string) (string, bool) {
raw = strings.TrimSpace(raw)
if raw == "" || len(raw) > 255 || raw == "*" || raw[0] == '-' || raw[len(raw)-1] == '-' {
return "", false
}
segmentLength := 0
for index := 0; index < len(raw); index++ {
char := raw[index]
if char == '-' {
if segmentLength == 0 || segmentLength > 8 {
return "", false
}
segmentLength = 0
continue
}
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) {
return "", false
}
segmentLength++
}
if segmentLength == 0 || segmentLength > 8 {
return "", false
}
tag, err := language.Parse(raw)
if err != nil || tag.String() == "" || tag.String() == "mul" {
return "", false
}
return tag.String(), true
}
func activityTemplateLanguageBase(locale string) string {
tag, err := language.Parse(locale)
if err != nil {
return ""
}
base, confidence := tag.Base()
if confidence == language.No || base.String() == "und" {
return ""
}
return base.String()
}
// preferredActivityTemplateAcceptLanguage applies RFC quality weights without accepting wildcard or malformed
// language ranges. Equal weights preserve header order; q=0 means explicitly unacceptable and is skipped.
func preferredActivityTemplateAcceptLanguage(raw string) (string, bool) {
bestLocale := ""
bestQuality := -1.0
for _, entry := range strings.Split(raw, ",") {
parts := strings.Split(entry, ";")
locale, valid := canonicalActivityTemplateLanguageTag(parts[0])
if !valid {
continue
}
quality := 1.0
validQuality := true
for _, parameter := range parts[1:] {
name, value, found := strings.Cut(strings.TrimSpace(parameter), "=")
if !found || !strings.EqualFold(strings.TrimSpace(name), "q") {
continue
}
parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || parsed < 0 || parsed > 1 {
validQuality = false
break
}
quality = parsed
}
if !validQuality || quality <= 0 || quality <= bestQuality {
continue
}
bestLocale = locale
bestQuality = quality
}
return bestLocale, bestLocale != ""
}

View File

@ -0,0 +1,567 @@
package activityapi
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
activityv1 "hyapp.local/api/proto/activity/v1"
userv1 "hyapp.local/api/proto/user/v1"
"hyapp/pkg/appcode"
"hyapp/services/gateway-service/internal/auth"
)
func TestGetCurrentActivityTemplateUsesAuthenticatedProfileRegionAndPublishedDTO(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{
currentResp: &activityv1.GetCurrentActivityTemplateResponse{
Found: true,
Runtime: &activityv1.ActivityTemplateRuntime{
VersionNo: 4, RuntimeFromMs: 1000, RuntimeToMs: 9000, ServerTimeMs: 2000,
Template: &activityv1.ActivityTemplate{
TemplateId: "tpl-1", TemplateCode: "ramadan-2026", Name: "admin-only-name",
ActivityType: "gift_challenge", Status: "published", LifecycleStatus: "running",
StartMs: 1000, EndMs: 9000, AllRegions: false, RegionIds: []int64{901},
Locales: []*activityv1.ActivityTemplateLocale{
{Locale: "en", Title: "Gift challenge", Rules: "rules-en"},
{Locale: "ar", Title: "تحدي الهدايا", Rules: "rules-ar"},
},
Gifts: []*activityv1.ActivityTemplateGift{{GiftId: "gift-1", Name: "Rose", IconUrl: "rose.png", CoinPrice: 20, PriceVersion: "v3"}},
Tasks: []*activityv1.ActivityTemplateTask{{TaskKey: "visit", TaskType: "daily_visit", TargetValue: 1, RewardResourceGroupId: 81}},
RankRewards: []*activityv1.ActivityTemplateRankReward{{BoardType: "daily", RankFrom: 1, RankTo: 1, ResourceGroupId: 82}},
Assets: []*activityv1.ActivityTemplateAsset{{AssetKey: "hero", Locale: "ar", Url: "hero.webp", MediaType: "webp"}},
DisplayConfigJson: `{"theme":"gold"}`, Revision: 99, CreatedByAdminId: 7,
},
},
},
}
profileClient := &fakeActivityTemplateProfileClient{regionID: 901}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: profileClient})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/current?locale=ar-EG&region_id=999999", "", 42001)
recorder := httptest.NewRecorder()
handler.getCurrentActivityTemplate(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
if profileClient.lastGet.GetUserId() != 42001 || profileClient.lastGet.GetMeta().GetAppCode() != "lalu" {
t.Fatalf("trusted region profile request mismatch: %+v", profileClient.lastGet)
}
if runtimeClient.lastCurrent.GetUserId() != 42001 || runtimeClient.lastCurrent.GetRegionId() != 901 || runtimeClient.lastCurrent.GetLocale() != "ar-EG" {
t.Fatalf("runtime request mismatch: %+v", runtimeClient.lastCurrent)
}
if runtimeClient.lastCurrent.GetMeta().GetAppCode() != "lalu" || runtimeClient.lastCurrent.GetMeta().GetRequestId() == "" {
t.Fatalf("activity meta mismatch: %+v", runtimeClient.lastCurrent.GetMeta())
}
var envelope struct {
Code string `json:"code"`
Data struct {
Found bool `json:"found"`
Runtime struct {
TemplateID string `json:"template_id"`
VersionNo int64 `json:"version_no"`
Locale struct {
Locale string `json:"locale"`
Title string `json:"title"`
} `json:"locale"`
DisplayConfig map[string]any `json:"display_config"`
} `json:"runtime"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Code != "OK" || !envelope.Data.Found || envelope.Data.Runtime.TemplateID != "tpl-1" || envelope.Data.Runtime.VersionNo != 4 {
t.Fatalf("runtime envelope mismatch: %+v", envelope)
}
if envelope.Data.Runtime.Locale.Locale != "ar" || envelope.Data.Runtime.Locale.Title != "تحدي الهدايا" || envelope.Data.Runtime.DisplayConfig["theme"] != "gold" {
t.Fatalf("locale/display config mismatch: %+v", envelope.Data.Runtime)
}
var rawEnvelope map[string]any
if err := json.Unmarshal(recorder.Body.Bytes(), &rawEnvelope); err != nil {
t.Fatalf("decode raw response: %v", err)
}
runtime := rawEnvelope["data"].(map[string]any)["runtime"].(map[string]any)
for _, adminField := range []string{"name", "revision", "created_by_admin_id", "updated_by_admin_id", "published_by_admin_id"} {
if _, exists := runtime[adminField]; exists {
t.Fatalf("app runtime leaked admin field %q: %+v", adminField, runtime)
}
}
}
func TestCurrentActivityTemplateLocaleFallsBackToEnglish(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{
currentResp: &activityv1.GetCurrentActivityTemplateResponse{
Found: true,
Runtime: &activityv1.ActivityTemplateRuntime{Template: &activityv1.ActivityTemplate{
TemplateId: "tpl-1",
Locales: []*activityv1.ActivityTemplateLocale{
{Locale: "ar", Title: "العربية"},
{Locale: "en", Title: "English fallback"},
},
}},
},
}
profileClient := &fakeActivityTemplateProfileClient{regionID: 901}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: profileClient})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/current", "", 42001)
request.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
recorder := httptest.NewRecorder()
handler.getCurrentActivityTemplate(recorder, request)
if recorder.Code != http.StatusOK || runtimeClient.lastCurrent.GetLocale() != "zh-CN" {
t.Fatalf("requested locale should be normalized before owner call: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastCurrent, recorder.Body.String())
}
var envelope struct {
Data struct {
Runtime struct {
Locale struct {
Locale string `json:"locale"`
Title string `json:"title"`
} `json:"locale"`
} `json:"runtime"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Data.Runtime.Locale.Locale != "en" || envelope.Data.Runtime.Locale.Title != "English fallback" {
t.Fatalf("unsupported locale must fall back to published English copy: %+v", envelope.Data.Runtime.Locale)
}
}
func TestActivityTemplateRequestLocalePreservesCanonicalBCP47AndSourcePriority(t *testing.T) {
tests := []struct {
name string
target string
appLanguage string
acceptLanguage string
want string
}{
{
name: "query keeps Chinese region", target: "/current?locale=zh-cn",
appLanguage: "pt-BR", acceptLanguage: "ar;q=1", want: "zh-CN",
},
{
name: "app header keeps Portuguese region", target: "/current",
appLanguage: "pt-br", acceptLanguage: "zh-CN;q=1", want: "pt-BR",
},
{
name: "accept language uses highest quality", target: "/current",
acceptLanguage: "fr-FR;q=0.2, zh-CN;q=0.7, pt-BR;q=0.9", want: "pt-BR",
},
{
name: "equal quality keeps client order", target: "/current",
acceptLanguage: "zh-CN;q=0.8, pt-BR;q=0.8", want: "zh-CN",
},
{
name: "invalid tags and q zero are never forwarded", target: "/current?locale=bad_tag",
appLanguage: "pt@@BR", acceptLanguage: "zh_CN;q=1, pt-BR;q=0, ar;q=NaN, en-US;q=0.5", want: "en-US",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, tt.target, nil)
request.Header.Set("X-App-Language", tt.appLanguage)
request.Header.Set("Accept-Language", tt.acceptLanguage)
if got := activityTemplateRequestLocale(request); got != tt.want {
t.Fatalf("activityTemplateRequestLocale() = %q, want %q", got, tt.want)
}
})
}
}
func TestSelectActivityTemplateLocaleUsesExactBaseSameLanguageThenEnglish(t *testing.T) {
tests := []struct {
name string
requested string
locales []activityTemplateLocaleData
want string
}{
{
name: "exact region", requested: "zh-CN",
locales: []activityTemplateLocaleData{{Locale: "zh-TW"}, {Locale: "zh-CN"}, {Locale: "en"}}, want: "zh-CN",
},
{
name: "language only before regional candidate", requested: "pt-AO",
locales: []activityTemplateLocaleData{{Locale: "pt-BR"}, {Locale: "pt"}, {Locale: "en"}}, want: "pt",
},
{
name: "same language uses configured order", requested: "pt-AO",
locales: []activityTemplateLocaleData{{Locale: "pt-BR"}, {Locale: "pt-PT"}, {Locale: "en"}}, want: "pt-BR",
},
{
name: "English after no language match", requested: "zh-CN",
locales: []activityTemplateLocaleData{{Locale: "ar"}, {Locale: "en"}}, want: "en",
},
{
name: "first after no English", requested: "zh-CN",
locales: []activityTemplateLocaleData{{Locale: "ar"}, {Locale: "pt-BR"}}, want: "ar",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
selected, found := selectActivityTemplateLocale(tt.locales, tt.requested)
if !found || selected.Locale != tt.want {
t.Fatalf("selectActivityTemplateLocale() = (%+v, %v), want locale %q", selected, found, tt.want)
}
})
}
}
func TestActivityTemplateRuntimeDropsInvalidPublishedLocaleTags(t *testing.T) {
runtime := activityTemplateRuntimeFromProto(&activityv1.ActivityTemplateRuntime{Template: &activityv1.ActivityTemplate{
TemplateId: "tpl-1",
Locales: []*activityv1.ActivityTemplateLocale{
{Locale: "bad_tag", Title: "must not leak"},
{Locale: "pt-br", Title: "Português"},
},
Assets: []*activityv1.ActivityTemplateAsset{
{AssetKey: "invalid", Locale: "zh_CN", Url: "invalid.webp"},
{AssetKey: "valid", Locale: "pt-br", Url: "valid.webp"},
},
}}, "pt-BR")
if len(runtime.Locales) != 1 || runtime.Locales[0].Locale != "pt-BR" || runtime.Locale == nil || runtime.Locale.Locale != "pt-BR" {
t.Fatalf("runtime locale sanitization mismatch: %+v", runtime)
}
if len(runtime.Assets) != 1 || runtime.Assets[0].Locale != "pt-BR" || runtime.Assets[0].AssetKey != "valid" {
t.Fatalf("runtime asset locale sanitization mismatch: %+v", runtime.Assets)
}
}
func TestVisitActivityTemplateKeepsClientCommandSeparateFromRequestID(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{visitResp: &activityv1.VisitActivityTemplateResponse{
FirstVisit: true,
Tasks: []*activityv1.ActivityTemplateRuntimeTask{{TemplateId: "tpl-1", TemplateCode: "summer", TaskDay: "2026-07-14", TaskKey: "visit", Status: "completed", Claimable: true}},
}}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 66}})
request := activityTemplateRequest(http.MethodPost, "/api/v1/activities/templates/summer/visit", `{"command_id":" visit-device-7 ","version_no":4}`, 777)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.visitActivityTemplate(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
req := runtimeClient.lastVisit
if req.GetUserId() != 777 || req.GetRegionId() != 66 || req.GetTemplateCode() != "summer" || req.GetCommandId() != "visit-device-7" || req.GetVersionNo() != 4 {
t.Fatalf("visit request mismatch: %+v", req)
}
if req.GetMeta().GetRequestId() == "" || req.GetMeta().GetRequestId() == req.GetCommandId() {
t.Fatalf("request_id and command_id must have independent semantics: meta=%+v command=%q", req.GetMeta(), req.GetCommandId())
}
var envelope struct {
Data struct {
FirstVisit bool `json:"first_visit"`
Tasks []struct {
TaskDay string `json:"task_day"`
Claimable bool `json:"claimable"`
} `json:"tasks"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil || !envelope.Data.FirstVisit || len(envelope.Data.Tasks) != 1 || !envelope.Data.Tasks[0].Claimable {
t.Fatalf("visit response mismatch: err=%v data=%+v", err, envelope.Data)
}
}
func TestActivityTemplateTasksAcceptOnlyCanonicalUTCDay(t *testing.T) {
t.Run("forwards canonical UTC day", func(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{tasksResp: &activityv1.ListActivityTemplateTasksResponse{NextRefreshAtMs: 1784159999000}}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 22}})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/tasks?task_day=2026-07-14", "", 88)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateTasks(recorder, request)
if recorder.Code != http.StatusOK || runtimeClient.lastTasks.GetTaskDay() != "2026-07-14" {
t.Fatalf("canonical UTC day mismatch: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastTasks, recorder.Body.String())
}
})
for _, invalid := range []string{"2026-7-14", "2026-07-14T00:00:00Z", "2026-02-30"} {
t.Run(invalid, func(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 22}})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/tasks?task_day="+invalid, "", 88)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateTasks(recorder, request)
if recorder.Code != http.StatusBadRequest || runtimeClient.lastTasks != nil {
t.Fatalf("invalid day should stop before gRPC: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastTasks, recorder.Body.String())
}
})
}
}
func TestHistoricalActivityTemplateTasksUseOwnedProgressAttributionInsteadOfCurrentProfileRegion(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{tasksResp: &activityv1.ListActivityTemplateTasksResponse{}}
profileClient := &fakeActivityTemplateProfileClient{regionID: 999}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: profileClient})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/tasks?version_no=3&task_day=2026-07-14", "", 88)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateTasks(recorder, request)
if recorder.Code != http.StatusOK || runtimeClient.lastTasks.GetVersionNo() != 3 || runtimeClient.lastTasks.GetRegionId() != 0 {
t.Fatalf("historical task lookup must be owner-attributed: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastTasks, recorder.Body.String())
}
if profileClient.lastGet != nil {
t.Fatalf("historical task lookup must not resolve mutable current region: %+v", profileClient.lastGet)
}
}
func TestClaimActivityTemplateTaskUsesAuthenticatedUserAndExplicitIdempotency(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{claimResp: &activityv1.ClaimActivityTemplateTaskRewardResponse{
Claimed: true,
Claim: &activityv1.ActivityTemplateTaskClaim{ClaimId: "claim-1", CommandId: "claim-device-1", UserId: 908, TaskDay: "2026-07-14", TaskKey: "gift-10", Status: "granted"},
}}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 33}})
request := activityTemplateRequest(http.MethodPost, "/api/v1/activities/templates/summer/tasks/gift-10/claim", `{"task_day":"2026-07-14","command_id":"claim-device-1","version_no":3,"user_id":1,"region_id":1}`, 908)
request.SetPathValue("template_code", "summer")
request.SetPathValue("task_key", "gift-10")
recorder := httptest.NewRecorder()
handler.claimActivityTemplateTask(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
req := runtimeClient.lastClaim
if req.GetUserId() != 908 || req.GetRegionId() != 0 || req.GetVersionNo() != 3 || req.GetTaskDay() != "2026-07-14" || req.GetTaskKey() != "gift-10" || req.GetCommandId() != "claim-device-1" {
t.Fatalf("claim request trusted fields mismatch: %+v", req)
}
if profile := handler.userProfileClient.(*fakeActivityTemplateProfileClient); profile.lastGet != nil {
t.Fatalf("exact-version claim must not resolve mutable current region: %+v", profile.lastGet)
}
var envelope struct {
Data struct {
Claimed bool `json:"claimed"`
Claim struct {
UserID string `json:"user_id"`
Status string `json:"status"`
} `json:"claim"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil || !envelope.Data.Claimed || envelope.Data.Claim.UserID != "908" || envelope.Data.Claim.Status != "granted" {
t.Fatalf("claim response mismatch: err=%v data=%+v", err, envelope.Data)
}
}
func TestActivityTemplateLeaderboardClampsPageAndBatchEnrichesProfiles(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{leaderboardResp: &activityv1.ListActivityTemplateLeaderboardResponse{
BoardType: "daily", PeriodKey: "2026-07-14", PeriodStartMs: 1000, PeriodEndMs: 2000, PeriodStatus: "pending", Total: 2,
Entries: []*activityv1.ActivityTemplateLeaderboardEntry{{RankNo: 1, UserId: 1001, Score: 800, GiftCount: 8, CoinAmount: 800, FirstScoredAtMs: 1100}},
MyEntry: &activityv1.ActivityTemplateLeaderboardEntry{RankNo: 2, UserId: 2002, Score: 700, GiftCount: 7, CoinAmount: 700, FirstScoredAtMs: 1200},
}}
profileClient := &fakeActivityTemplateProfileClient{
regionID: 45,
users: map[int64]*userv1.User{
1001: {UserId: 1001, DisplayUserId: "L1001", Username: "first", Avatar: "first.png"},
2002: {UserId: 2002, DisplayUserId: "L2002", Username: "me", Avatar: "me.png"},
},
}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: profileClient})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/daily?version_no=4&period_key=2026-07-14&page=2&page_size=999", "", 2002)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateDailyLeaderboard(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got=%d body=%s", recorder.Code, recorder.Body.String())
}
req := runtimeClient.lastLeaderboard
if req.GetUserId() != 2002 || req.GetRegionId() != 0 || req.GetVersionNo() != 4 || req.GetBoardType() != "daily" || req.GetPeriodKey() != "2026-07-14" || req.GetPage() != 2 || req.GetPageSize() != 100 {
t.Fatalf("leaderboard request mismatch: %+v", req)
}
if profileClient.lastGet != nil {
t.Fatalf("exact-version leaderboard must not resolve mutable current region: %+v", profileClient.lastGet)
}
if got := profileClient.lastBatch.GetUserIds(); len(got) != 2 || got[0] != 1001 || got[1] != 2002 {
t.Fatalf("profile batch should contain page and my-entry users once: %+v", got)
}
var envelope struct {
Data struct {
Page int32 `json:"page"`
PageSize int32 `json:"page_size"`
Entries []struct {
UserID string `json:"user_id"`
User struct {
DisplayUserID string `json:"display_user_id"`
Username string `json:"username"`
} `json:"user"`
} `json:"entries"`
MyEntry struct {
User struct {
Avatar string `json:"avatar"`
} `json:"user"`
} `json:"my_entry"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
t.Fatalf("decode response: %v", err)
}
if envelope.Data.Page != 2 || envelope.Data.PageSize != 100 || len(envelope.Data.Entries) != 1 || envelope.Data.Entries[0].UserID != "1001" || envelope.Data.Entries[0].User.DisplayUserID != "L1001" || envelope.Data.MyEntry.User.Avatar != "me.png" {
t.Fatalf("leaderboard response mismatch: %+v", envelope.Data)
}
}
func TestDailyLeaderboardForwardsExplicitHistoricalUTCPeriod(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{leaderboardResp: &activityv1.ListActivityTemplateLeaderboardResponse{
BoardType: "daily", PeriodKey: "2026-06-01", PeriodStatus: "settled",
Entries: []*activityv1.ActivityTemplateLeaderboardEntry{{RankNo: 1, UserId: 1001, Snapshot: true}},
}}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 45}})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/daily?period_key=2026-06-01", "", 2002)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateDailyLeaderboard(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("settled historical period should reach owner: code=%d body=%s", recorder.Code, recorder.Body.String())
}
if runtimeClient.lastLeaderboard.GetPeriodKey() != "2026-06-01" || runtimeClient.lastLeaderboard.GetBoardType() != "daily" {
t.Fatalf("gateway must preserve explicit historical UTC period: %+v", runtimeClient.lastLeaderboard)
}
}
func TestDailyLeaderboardRejectsNonCanonicalUTCPeriod(t *testing.T) {
for _, invalid := range []string{"2026-6-1", "2026-06-01T00:00:00Z", "2026-02-30"} {
t.Run(invalid, func(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: &fakeActivityTemplateProfileClient{regionID: 45}})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/daily?period_key="+invalid, "", 2002)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateDailyLeaderboard(recorder, request)
if recorder.Code != http.StatusBadRequest || runtimeClient.lastLeaderboard != nil {
t.Fatalf("non-canonical UTC period must stop before owner: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastLeaderboard, recorder.Body.String())
}
})
}
}
func TestActivityTemplateLeaderboardProfileDecorationFailureDegradesToFacts(t *testing.T) {
runtimeClient := &fakeActivityTemplateRuntimeClient{leaderboardResp: &activityv1.ListActivityTemplateLeaderboardResponse{
BoardType: "total", PeriodKey: "total", Total: 1,
Entries: []*activityv1.ActivityTemplateLeaderboardEntry{{RankNo: 1, UserId: 1001, Score: 800}},
}}
profileClient := &fakeActivityTemplateProfileClient{regionID: 45, batchErr: errors.New("user profile unavailable")}
handler := New(Config{ActivityTemplateRuntime: runtimeClient, UserProfileClient: profileClient})
request := activityTemplateRequest(http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/total", "", 2002)
request.SetPathValue("template_code", "summer")
recorder := httptest.NewRecorder()
handler.listActivityTemplateTotalLeaderboard(recorder, request)
if recorder.Code != http.StatusOK || runtimeClient.lastLeaderboard.GetPeriodKey() != "total" {
t.Fatalf("profile decoration must not hide leaderboard facts: code=%d req=%+v body=%s", recorder.Code, runtimeClient.lastLeaderboard, recorder.Body.String())
}
var envelope struct {
Data struct {
Entries []map[string]any `json:"entries"`
} `json:"data"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil || len(envelope.Data.Entries) != 1 {
t.Fatalf("decode facts response: err=%v data=%+v", err, envelope.Data)
}
if _, decorated := envelope.Data.Entries[0]["user"]; decorated {
t.Fatalf("failed best-effort profile lookup should omit user decoration: %+v", envelope.Data.Entries[0])
}
}
func activityTemplateRequest(method, target, body string, userID int64) *http.Request {
request := httptest.NewRequest(method, target, strings.NewReader(body))
ctx := appcode.WithContext(request.Context(), "lalu")
ctx = auth.WithUserID(ctx, userID)
return request.WithContext(ctx)
}
type fakeActivityTemplateRuntimeClient struct {
currentResp *activityv1.GetCurrentActivityTemplateResponse
visitResp *activityv1.VisitActivityTemplateResponse
tasksResp *activityv1.ListActivityTemplateTasksResponse
claimResp *activityv1.ClaimActivityTemplateTaskRewardResponse
leaderboardResp *activityv1.ListActivityTemplateLeaderboardResponse
lastCurrent *activityv1.GetCurrentActivityTemplateRequest
lastVisit *activityv1.VisitActivityTemplateRequest
lastTasks *activityv1.ListActivityTemplateTasksRequest
lastClaim *activityv1.ClaimActivityTemplateTaskRewardRequest
lastLeaderboard *activityv1.ListActivityTemplateLeaderboardRequest
}
func (f *fakeActivityTemplateRuntimeClient) GetCurrentActivityTemplate(_ context.Context, req *activityv1.GetCurrentActivityTemplateRequest) (*activityv1.GetCurrentActivityTemplateResponse, error) {
f.lastCurrent = req
if f.currentResp != nil {
return f.currentResp, nil
}
return &activityv1.GetCurrentActivityTemplateResponse{}, nil
}
func (f *fakeActivityTemplateRuntimeClient) VisitActivityTemplate(_ context.Context, req *activityv1.VisitActivityTemplateRequest) (*activityv1.VisitActivityTemplateResponse, error) {
f.lastVisit = req
if f.visitResp != nil {
return f.visitResp, nil
}
return &activityv1.VisitActivityTemplateResponse{}, nil
}
func (f *fakeActivityTemplateRuntimeClient) ListActivityTemplateTasks(_ context.Context, req *activityv1.ListActivityTemplateTasksRequest) (*activityv1.ListActivityTemplateTasksResponse, error) {
f.lastTasks = req
if f.tasksResp != nil {
return f.tasksResp, nil
}
return &activityv1.ListActivityTemplateTasksResponse{}, nil
}
func (f *fakeActivityTemplateRuntimeClient) ClaimActivityTemplateTaskReward(_ context.Context, req *activityv1.ClaimActivityTemplateTaskRewardRequest) (*activityv1.ClaimActivityTemplateTaskRewardResponse, error) {
f.lastClaim = req
if f.claimResp != nil {
return f.claimResp, nil
}
return &activityv1.ClaimActivityTemplateTaskRewardResponse{}, nil
}
func (f *fakeActivityTemplateRuntimeClient) ListActivityTemplateLeaderboard(_ context.Context, req *activityv1.ListActivityTemplateLeaderboardRequest) (*activityv1.ListActivityTemplateLeaderboardResponse, error) {
f.lastLeaderboard = req
if f.leaderboardResp != nil {
return f.leaderboardResp, nil
}
return &activityv1.ListActivityTemplateLeaderboardResponse{}, nil
}
type fakeActivityTemplateProfileClient struct {
regionID int64
users map[int64]*userv1.User
batchErr error
lastGet *userv1.GetUserRequest
lastBatch *userv1.BatchGetUsersRequest
}
func (f *fakeActivityTemplateProfileClient) GetUser(_ context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
f.lastGet = req
return &userv1.GetUserResponse{User: &userv1.User{UserId: req.GetUserId(), RegionId: f.regionID}}, nil
}
func (f *fakeActivityTemplateProfileClient) BatchGetUsers(_ context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error) {
f.lastBatch = req
if f.batchErr != nil {
return nil, f.batchErr
}
return &userv1.BatchGetUsersResponse{Users: f.users}, nil
}

View File

@ -3,6 +3,7 @@ package activityapi
import ( import (
"context" "context"
userv1 "hyapp.local/api/proto/user/v1"
"hyapp/pkg/userleaderboard" "hyapp/pkg/userleaderboard"
"hyapp/services/gateway-service/internal/client" "hyapp/services/gateway-service/internal/client"
"hyapp/services/gateway-service/internal/transport/http/httproutes" "hyapp/services/gateway-service/internal/transport/http/httproutes"
@ -14,7 +15,7 @@ type Handler struct {
taskClient client.TaskClient taskClient client.TaskClient
growthLevelClient client.GrowthLevelClient growthLevelClient client.GrowthLevelClient
achievementClient client.AchievementClient achievementClient client.AchievementClient
userProfileClient client.UserProfileClient userProfileClient UserProfileReader
roomQueryClient client.RoomQueryClient roomQueryClient client.RoomQueryClient
walletClient client.WalletClient walletClient client.WalletClient
userLeaderboard UserLeaderboardStore userLeaderboard UserLeaderboardStore
@ -29,6 +30,8 @@ type Handler struct {
cpWeeklyRank client.CPWeeklyRankClient cpWeeklyRank client.CPWeeklyRankClient
agencyOpening client.AgencyOpeningClient agencyOpening client.AgencyOpeningClient
wheel client.WheelClient wheel client.WheelClient
activityTemplateRuntime client.ActivityTemplateRuntimeClient
} }
type UserLeaderboardStore interface { type UserLeaderboardStore interface {
@ -36,11 +39,19 @@ type UserLeaderboardStore interface {
RankForUser(ctx context.Context, query userleaderboard.Query, userID int64) (userleaderboard.Entry, bool, error) RankForUser(ctx context.Context, query userleaderboard.Query, userID int64) (userleaderboard.Entry, bool, error)
} }
// UserProfileReader keeps activity HTTP composition on the two profile operations it actually needs.
// The concrete gRPC client exposes more user commands, but widening this boundary would let activity handlers
// accidentally mutate user ownership while only resolving a trusted region and leaderboard presentation data.
type UserProfileReader interface {
GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error)
BatchGetUsers(ctx context.Context, req *userv1.BatchGetUsersRequest) (*userv1.BatchGetUsersResponse, error)
}
type Config struct { type Config struct {
TaskClient client.TaskClient TaskClient client.TaskClient
GrowthLevelClient client.GrowthLevelClient GrowthLevelClient client.GrowthLevelClient
AchievementClient client.AchievementClient AchievementClient client.AchievementClient
UserProfileClient client.UserProfileClient UserProfileClient UserProfileReader
RoomQueryClient client.RoomQueryClient RoomQueryClient client.RoomQueryClient
WalletClient client.WalletClient WalletClient client.WalletClient
UserLeaderboard UserLeaderboardStore UserLeaderboard UserLeaderboardStore
@ -55,6 +66,8 @@ type Config struct {
CPWeeklyRank client.CPWeeklyRankClient CPWeeklyRank client.CPWeeklyRankClient
AgencyOpening client.AgencyOpeningClient AgencyOpening client.AgencyOpeningClient
Wheel client.WheelClient Wheel client.WheelClient
ActivityTemplateRuntime client.ActivityTemplateRuntimeClient
} }
func New(config Config) *Handler { func New(config Config) *Handler {
@ -77,6 +90,8 @@ func New(config Config) *Handler {
cpWeeklyRank: config.CPWeeklyRank, cpWeeklyRank: config.CPWeeklyRank,
agencyOpening: config.AgencyOpening, agencyOpening: config.AgencyOpening,
wheel: config.Wheel, wheel: config.Wheel,
activityTemplateRuntime: config.ActivityTemplateRuntime,
} }
} }
@ -106,6 +121,13 @@ func (h *Handler) TaskHandlers() httproutes.TaskHandlers {
DrawWheel: h.drawWheel, DrawWheel: h.drawWheel,
ListWheelHistory: h.listWheelHistory, ListWheelHistory: h.listWheelHistory,
ListWheelHints: h.listWheelHints, ListWheelHints: h.listWheelHints,
GetCurrentActivityTemplate: h.getCurrentActivityTemplate,
VisitActivityTemplate: h.visitActivityTemplate,
ListActivityTemplateTasks: h.listActivityTemplateTasks,
ClaimActivityTemplateTask: h.claimActivityTemplateTask,
ListActivityTemplateDailyLeaderboard: h.listActivityTemplateDailyLeaderboard,
ListActivityTemplateTotalLeaderboard: h.listActivityTemplateTotalLeaderboard,
} }
} }

View File

@ -78,6 +78,8 @@ type Handler struct {
giftMaxUnits int giftMaxUnits int
loginRiskConfig LoginRiskConfig loginRiskConfig LoginRiskConfig
loginRiskCache loginRiskCache loginRiskCache loginRiskCache
activityTemplateRuntime client.ActivityTemplateRuntimeClient
} }
// TencentIMConfig 是 gateway transport 层签发客户端腾讯云 IM 登录票据所需的配置。 // TencentIMConfig 是 gateway transport 层签发客户端腾讯云 IM 登录票据所需的配置。
@ -344,6 +346,12 @@ func (h *Handler) SetWheelClient(wheel client.WheelClient) {
h.wheel = wheel h.wheel = wheel
} }
// SetActivityTemplateRuntimeClient 注入已发布活动模版的 App 运行态入口。
// gateway 只增加鉴权和资料展示编排,不在 HTTP 层复制版本、任务、榜单或发奖状态机。
func (h *Handler) SetActivityTemplateRuntimeClient(runtime client.ActivityTemplateRuntimeClient) {
h.activityTemplateRuntime = runtime
}
// SetBroadcastClient 注入 activity-service 播报群成员关系 client。 // SetBroadcastClient 注入 activity-service 播报群成员关系 client。
func (h *Handler) SetBroadcastClient(broadcastClient client.BroadcastClient) { func (h *Handler) SetBroadcastClient(broadcastClient client.BroadcastClient) {
h.broadcastClient = broadcastClient h.broadcastClient = broadcastClient

View File

@ -0,0 +1,69 @@
package httproutes
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestActivityTemplateRuntimeRoutesAreProfileProtectedAndPreservePathValues(t *testing.T) {
profileCalls := 0
profileWrap := func(next http.HandlerFunc) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
profileCalls++
next(writer, request)
})
}
identityWrap := func(next http.HandlerFunc) http.Handler { return next }
marker := func(name string) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("X-Handler", name)
writer.Header().Set("X-Template-Code", request.PathValue("template_code"))
writer.Header().Set("X-Task-Key", request.PathValue("task_key"))
writer.WriteHeader(http.StatusNoContent)
}
}
mux := http.NewServeMux()
routeSet := routes{mux: mux, config: Config{
PublicWrap: identityWrap, AuthWrap: identityWrap, ProfileWrap: profileWrap,
Task: TaskHandlers{
GetCurrentActivityTemplate: marker("current"),
VisitActivityTemplate: marker("visit"),
ListActivityTemplateTasks: marker("tasks"),
ClaimActivityTemplateTask: marker("claim"),
ListActivityTemplateDailyLeaderboard: marker("daily"),
ListActivityTemplateTotalLeaderboard: marker("total"),
},
}}
routeSet.registerTaskRoutes()
tests := []struct {
method string
path string
handler string
templateCode string
taskKey string
}{
{http.MethodGet, "/api/v1/activities/templates/current", "current", "", ""},
{http.MethodPost, "/api/v1/activities/templates/summer/visit", "visit", "summer", ""},
{http.MethodGet, "/api/v1/activities/templates/summer/tasks", "tasks", "summer", ""},
{http.MethodPost, "/api/v1/activities/templates/summer/tasks/gift-10/claim", "claim", "summer", "gift-10"},
{http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/daily", "daily", "summer", ""},
{http.MethodGet, "/api/v1/activities/templates/summer/leaderboards/total", "total", "summer", ""},
}
for _, test := range tests {
t.Run(test.handler, func(t *testing.T) {
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, httptest.NewRequest(test.method, test.path, nil))
if recorder.Code != http.StatusNoContent || recorder.Header().Get("X-Handler") != test.handler {
t.Fatalf("route mismatch: code=%d handler=%q body=%s", recorder.Code, recorder.Header().Get("X-Handler"), recorder.Body.String())
}
if recorder.Header().Get("X-Template-Code") != test.templateCode || recorder.Header().Get("X-Task-Key") != test.taskKey {
t.Fatalf("path values mismatch: template=%q task=%q", recorder.Header().Get("X-Template-Code"), recorder.Header().Get("X-Task-Key"))
}
})
}
if profileCalls != len(tests) {
t.Fatalf("all activity template routes must use profile middleware: got=%d want=%d", profileCalls, len(tests))
}
}

View File

@ -237,6 +237,13 @@ type TaskHandlers struct {
DrawWheel http.HandlerFunc DrawWheel http.HandlerFunc
ListWheelHistory http.HandlerFunc ListWheelHistory http.HandlerFunc
ListWheelHints http.HandlerFunc ListWheelHints http.HandlerFunc
GetCurrentActivityTemplate http.HandlerFunc
VisitActivityTemplate http.HandlerFunc
ListActivityTemplateTasks http.HandlerFunc
ClaimActivityTemplateTask http.HandlerFunc
ListActivityTemplateDailyLeaderboard http.HandlerFunc
ListActivityTemplateTotalLeaderboard http.HandlerFunc
} }
type LevelHandlers struct { type LevelHandlers struct {
@ -619,6 +626,12 @@ func (r routes) registerTaskRoutes() {
r.profile("/activities/agency-opening-event", http.MethodGet, h.GetAgencyOpeningStatus) r.profile("/activities/agency-opening-event", http.MethodGet, h.GetAgencyOpeningStatus)
r.profile("/activities/agency-opening-event/apply", http.MethodPost, h.ApplyAgencyOpening) r.profile("/activities/agency-opening-event/apply", http.MethodPost, h.ApplyAgencyOpening)
r.profile("/activities/agency-opening-event/leaderboard", http.MethodGet, h.ListAgencyOpeningLeaderboard) r.profile("/activities/agency-opening-event/leaderboard", http.MethodGet, h.ListAgencyOpeningLeaderboard)
r.profile("/activities/templates/current", http.MethodGet, h.GetCurrentActivityTemplate)
r.profile("/activities/templates/{template_code}/visit", http.MethodPost, h.VisitActivityTemplate)
r.profile("/activities/templates/{template_code}/tasks", http.MethodGet, h.ListActivityTemplateTasks)
r.profile("/activities/templates/{template_code}/tasks/{task_key}/claim", http.MethodPost, h.ClaimActivityTemplateTask)
r.profile("/activities/templates/{template_code}/leaderboards/daily", http.MethodGet, h.ListActivityTemplateDailyLeaderboard)
r.profile("/activities/templates/{template_code}/leaderboards/total", http.MethodGet, h.ListActivityTemplateTotalLeaderboard)
r.public("/activity/wheel/{wheel_id}/config", http.MethodGet, h.GetWheelConfig) r.public("/activity/wheel/{wheel_id}/config", http.MethodGet, h.GetWheelConfig)
r.profile("/activity/wheel/{wheel_id}/draw", http.MethodPost, h.DrawWheel) r.profile("/activity/wheel/{wheel_id}/draw", http.MethodPost, h.DrawWheel)
r.profile("/activity/wheel/{wheel_id}/history", http.MethodPost, h.ListWheelHistory) r.profile("/activity/wheel/{wheel_id}/history", http.MethodPost, h.ListWheelHistory)

View File

@ -213,7 +213,7 @@ func (h *Handler) serveRoomGiftPanel(writer http.ResponseWriter, request *http.R
} }
panelGifts := append([]giftConfigData{}, panelConfig.Gifts...) panelGifts := append([]giftConfigData{}, panelConfig.Gifts...)
panelGifts = append(panelGifts, roomGiftBagGiftsFromResources(bagResources, panelConfig.Gifts)...) panelGifts = append(panelGifts, roomGiftBagGiftsFromResources(bagResources, panelConfig.Gifts, snapshot.GetVisibleRegionId())...)
data := roomGiftPanelData{ data := roomGiftPanelData{
CoinBalance: coinBalanceFromProto(balances), CoinBalance: coinBalanceFromProto(balances),
Recipients: roomGiftRecipients(snapshot, recipientProfiles), Recipients: roomGiftRecipients(snapshot, recipientProfiles),

View File

@ -962,17 +962,6 @@ func (h *Handler) roomGiftCatalogVersion(request *http.Request, app string) (int
return resp.GetVersion(), true, nil return resp.GetVersion(), true, nil
} }
func (h *Handler) roomGiftBagGifts(request *http.Request, viewerUserID int64, gifts []giftConfigData) ([]giftConfigData, error) {
if viewerUserID <= 0 || h.walletClient == nil || len(gifts) == 0 {
return nil, nil
}
resp, err := h.roomGiftBagResources(request, viewerUserID)
if err != nil {
return nil, err
}
return roomGiftBagGiftsFromResources(resp, gifts), nil
}
func (h *Handler) roomGiftBagResources(request *http.Request, viewerUserID int64) (*walletv1.ListUserResourcesResponse, error) { func (h *Handler) roomGiftBagResources(request *http.Request, viewerUserID int64) (*walletv1.ListUserResourcesResponse, error) {
if viewerUserID <= 0 || h.walletClient == nil { if viewerUserID <= 0 || h.walletClient == nil {
return &walletv1.ListUserResourcesResponse{}, nil return &walletv1.ListUserResourcesResponse{}, nil
@ -986,8 +975,8 @@ func (h *Handler) roomGiftBagResources(request *http.Request, viewerUserID int64
}) })
} }
func roomGiftBagGiftsFromResources(resp *walletv1.ListUserResourcesResponse, gifts []giftConfigData) []giftConfigData { func roomGiftBagGiftsFromResources(resp *walletv1.ListUserResourcesResponse, gifts []giftConfigData, visibleRegionID int64) []giftConfigData {
if resp == nil || len(gifts) == 0 { if resp == nil {
return nil return nil
} }
giftByResourceID := make(map[int64]giftConfigData, len(gifts)) giftByResourceID := make(map[int64]giftConfigData, len(gifts))
@ -1005,19 +994,49 @@ func roomGiftBagGiftsFromResources(resp *walletv1.ListUserResourcesResponse, gif
if remaining <= 0 { if remaining <= 0 {
continue continue
} }
if item.GetSourceSnapshotId() != "" {
// Pinned activity rewards are wallet-owned promises. Current catalog disable/edit must not hide or
// rewrite them; wallet already verified hash/app/resource binding before returning these configs.
for _, pinned := range item.GetPinnedGiftConfigs() {
if !giftProtoAvailableInRegion(pinned, visibleRegionID) {
continue
}
gift := giftFromProto(pinned)
gift = roomGiftBagConfig(gift, item, remaining)
bagGifts = append(bagGifts, gift)
}
continue
}
gift, ok := giftByResourceID[item.GetResourceId()] gift, ok := giftByResourceID[item.GetResourceId()]
if !ok { if !ok {
continue continue
} }
// Bag tab 只承载用户实际拥有的 gift 权益;仍复用 gift 配置的价格/动效,避免背包资源和房间送礼协议出现两套展示口径。 // Bag tab 只承载用户实际拥有的 gift 权益;仍复用 gift 配置的价格/动效,避免背包资源和房间送礼协议出现两套展示口径。
gift = roomGiftBagConfig(gift, item, remaining)
bagGifts = append(bagGifts, gift)
}
return bagGifts
}
func roomGiftBagConfig(gift giftConfigData, item *walletv1.UserResourceEntitlement, remaining int64) giftConfigData {
gift.GiftTypeCode = "bag" gift.GiftTypeCode = "bag"
gift.Source = "bag" gift.Source = "bag"
gift.EntitlementID = item.GetEntitlementId() gift.EntitlementID = item.GetEntitlementId()
gift.OwnedQuantity = item.GetQuantity() gift.OwnedQuantity = item.GetQuantity()
gift.RemainingQuantity = remaining gift.RemainingQuantity = remaining
bagGifts = append(bagGifts, gift) return gift
} }
return bagGifts
func giftProtoAvailableInRegion(gift *walletv1.GiftConfig, regionID int64) bool {
if gift == nil {
return false
}
for _, allowed := range gift.GetRegionIds() {
if allowed == 0 || allowed == regionID {
return true
}
}
return false
} }
// getRoomRocket 返回房间火箭物料配置和当前进度。 // getRoomRocket 返回房间火箭物料配置和当前进度。

View File

@ -9,6 +9,7 @@ import (
"time" "time"
roomv1 "hyapp.local/api/proto/room/v1" roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
) )
func TestCreateRoomRejectsMissingRoomAvatar(t *testing.T) { func TestCreateRoomRejectsMissingRoomAvatar(t *testing.T) {
@ -154,6 +155,46 @@ func TestRoomGiftTabsIncludesBagForOwnedGifts(t *testing.T) {
} }
} }
func TestRoomGiftBagPanelUsesPinnedCatalogAfterLiveGiftEditOrDisable(t *testing.T) {
pinned := &walletv1.GiftConfig{
GiftId: "season-star", ResourceId: 501, Status: "active", Name: "Season Star V1", PriceVersion: "v1",
CoinPrice: 7, GiftTypeCode: "cp", CpRelationType: "brother", RegionIds: []int64{1},
PresentationJson: `{"theme":"season-one"}`,
Resource: &walletv1.Resource{ResourceId: 501, ResourceType: "gift", Status: "active", Name: "Season Star V1",
PreviewUrl: "https://static.example.test/season-star-v1.png", AnimationUrl: "https://static.example.test/season-star-v1.svga"},
}
resources := &walletv1.ListUserResourcesResponse{Resources: []*walletv1.UserResourceEntitlement{{
EntitlementId: "ent-pinned-star", ResourceId: 501, Quantity: 3, RemainingQuantity: 2,
SourceSnapshotId: "rgs-season-one", PinnedGiftConfigs: []*walletv1.GiftConfig{pinned},
}}}
liveV2 := giftConfigData{
GiftID: "season-star", ResourceID: 501, Name: "Mutable Star V2", PriceVersion: "v2", CoinPrice: 31,
Resource: resourceData{ResourceID: 501, ResourceType: "gift", PreviewURL: "https://static.example.test/season-star-v2.png"},
}
panel := roomGiftBagGiftsFromResources(resources, []giftConfigData{liveV2}, 1)
if len(panel) != 1 || panel[0].Name != "Season Star V1" || panel[0].PriceVersion != "v1" || panel[0].CoinPrice != 7 ||
panel[0].Resource.PreviewURL != "https://static.example.test/season-star-v1.png" || panel[0].EntitlementID != "ent-pinned-star" ||
panel[0].Source != "bag" || panel[0].RemainingQuantity != 2 {
t.Fatalf("pinned bag panel drifted to live V2 catalog: %+v", panel)
}
// A disabled gift is absent from the live catalog, but the verified entitlement snapshot remains visible.
panel = roomGiftBagGiftsFromResources(resources, nil, 1)
if len(panel) != 1 || panel[0].Name != "Season Star V1" {
t.Fatalf("disabled pinned gift disappeared from bag panel: %+v", panel)
}
if hidden := roomGiftBagGiftsFromResources(resources, nil, 2); len(hidden) != 0 {
t.Fatalf("pinned gift leaked outside its pin-time region: %+v", hidden)
}
// Legacy inventory has no immutable gift bytes and therefore deliberately follows the current catalog.
resources.Resources[0].SourceSnapshotId = ""
resources.Resources[0].PinnedGiftConfigs = nil
legacy := roomGiftBagGiftsFromResources(resources, []giftConfigData{liveV2}, 1)
if len(legacy) != 1 || legacy[0].Name != "Mutable Star V2" || legacy[0].PriceVersion != "v2" {
t.Fatalf("legacy bag entitlement did not fall back to live catalog: %+v", legacy)
}
}
func TestCreateRoomDataIncludesExplicitIMGroupID(t *testing.T) { func TestCreateRoomDataIncludesExplicitIMGroupID(t *testing.T) {
data := createRoomDataFromProto(&roomv1.CreateRoomResponse{ data := createRoomDataFromProto(&roomv1.CreateRoomResponse{
Result: &roomv1.CommandResult{Applied: true, RoomVersion: 1, ServerTimeMs: 1700000000000}, Result: &roomv1.CommandResult{Applied: true, RoomVersion: 1, ServerTimeMs: 1700000000000},

View File

@ -103,6 +103,8 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler {
CPWeeklyRank: h.cpWeeklyRank, CPWeeklyRank: h.cpWeeklyRank,
AgencyOpening: h.agencyOpening, AgencyOpening: h.agencyOpening,
Wheel: h.wheel, Wheel: h.wheel,
ActivityTemplateRuntime: h.activityTemplateRuntime,
}) })
gameAPI := gameapi.New(gameapi.Config{ gameAPI := gameapi.New(gameapi.Config{
GameClient: h.gameClient, GameClient: h.gameClient,

Some files were not shown because too many files have changed in this diff Show More