Merge remote-tracking branch 'origin/main' into test

# Conflicts:
#	api/proto/user/v1/user.pb.go
#	server/admin/internal/modules/appconfig/service_test.go
#	server/admin/migrations/086_app_config_app_scope.sql
#	services/cron-service/configs/config.docker.yaml
#	services/cron-service/configs/config.tencent.example.yaml
#	services/cron-service/configs/config.yaml
This commit is contained in:
zhx 2026-07-15 13:33:44 +08:00
commit 723b77f137
155 changed files with 43077 additions and 29159 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,17 +24,16 @@ 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 EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
unknownFields protoimpl.UnknownFields 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"`
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"`
AppCode string `protobuf:"bytes,3,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
// draw_id 是本次送礼命令的聚合中奖标识;批量子抽明细仍只保存在 lucky owner 数据库,不能放大 MQ 负载。 // draw_id 是本次送礼命令的聚合中奖标识;批量子抽明细仍只保存在 lucky owner 数据库,不能放大 MQ 负载。
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,18 +112,15 @@ 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 DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"`
unknownFields protoimpl.UnknownFields 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"`
DrawId string `protobuf:"bytes,1,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` PoolId string `protobuf:"bytes,4,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` GiftId string `protobuf:"bytes,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"`
RoomId string `protobuf:"bytes,3,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` GiftCount int32 `protobuf:"varint,6,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"`
PoolId string `protobuf:"bytes,4,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` SenderUserId int64 `protobuf:"varint,7,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"`
GiftId string `protobuf:"bytes,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` TargetUserId int64 `protobuf:"varint,8,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"`
GiftCount int32 `protobuf:"varint,6,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"`
SenderUserId int64 `protobuf:"varint,7,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"`
TargetUserId int64 `protobuf:"varint,8,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"`
// 展示资料是送礼入口固化的快照;消费者不应为了飘屏高频同步反查 user-service。 // 展示资料是送礼入口固化的快照;消费者不应为了飘屏高频同步反查 user-service。
SenderName string `protobuf:"bytes,9,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"` SenderName string `protobuf:"bytes,9,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"`
SenderAvatar string `protobuf:"bytes,10,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,omitempty"` SenderAvatar string `protobuf:"bytes,10,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,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 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"`
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"`
AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
AppCode string `protobuf:"bytes,5,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
} }
func (x *RequestMeta) Reset() { func (x *RequestMeta) Reset() {
@ -99,21 +99,20 @@ func (x *RequestMeta) GetAppCode() string {
} }
type Robot struct { type Robot struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
unknownFields protoimpl.UnknownFields 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"`
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` RoomScene string `protobuf:"bytes,4,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"`
RobotScope string `protobuf:"bytes,2,opt,name=robot_scope,json=robotScope,proto3" json:"robot_scope,omitempty"` UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
GameId string `protobuf:"bytes,3,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"`
RoomScene string `protobuf:"bytes,4,opt,name=room_scene,json=roomScene,proto3" json:"room_scene,omitempty"` CreatedByAdminId int64 `protobuf:"varint,7,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"`
UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
CreatedByAdminId int64 `protobuf:"varint,7,opt,name=created_by_admin_id,json=createdByAdminId,proto3" json:"created_by_admin_id,omitempty"` LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"`
CreatedAtMs int64 `protobuf:"varint,8,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"`
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` unknownFields protoimpl.UnknownFields
LastUsedAtMs int64 `protobuf:"varint,10,opt,name=last_used_at_ms,json=lastUsedAtMs,proto3" json:"last_used_at_ms,omitempty"` sizeCache protoimpl.SizeCache
UsedCount int64 `protobuf:"varint,11,opt,name=used_count,json=usedCount,proto3" json:"used_count,omitempty"`
} }
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 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"`
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"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
} }
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 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"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
} }
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 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"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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 Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
unknownFields protoimpl.UnknownFields 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"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"`
GameId string `protobuf:"bytes,2,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"` ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"`
MatchId string `protobuf:"bytes,3,opt,name=match_id,json=matchId,proto3" json:"match_id,omitempty"` unknownFields protoimpl.UnknownFields
SelectedAtMs int64 `protobuf:"varint,4,opt,name=selected_at_ms,json=selectedAtMs,proto3" json:"selected_at_ms,omitempty"` sizeCache protoimpl.SizeCache
ExcludeUserIds []int64 `protobuf:"varint,5,rep,packed,name=exclude_user_ids,json=excludeUserIds,proto3" json:"exclude_user_ids,omitempty"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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 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"`
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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
} }
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 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"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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 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"`
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"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
Cursor string `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
} }
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 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"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
} }
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 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"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
UserIds []int64 `protobuf:"varint,3,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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 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"`
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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
} }
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 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"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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 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"`
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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
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"`
} }
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 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"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
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"`
} }
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.

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.
@ -2578,7 +2578,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) ListApps(context.Context, *ListAppsRequest) (*ListAppsResponse, error) { func (UnimplementedAppRegistryServiceServer) ListApps(context.Context, *ListAppsRequest) (*ListAppsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListApps not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListApps not implemented")
@ -2594,7 +2594,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.
@ -2724,10 +2724,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() {}
@ -2740,7 +2740,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.
@ -2868,10 +2868,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() {}
@ -2884,7 +2884,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.
@ -3040,16 +3040,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() {}
@ -3062,7 +3062,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.
@ -3299,25 +3299,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() {}
@ -3330,7 +3330,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.
@ -3646,28 +3646,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() {
} }
@ -3681,7 +3681,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;
@ -1803,6 +1861,9 @@ message VerifyCoinSellerRechargeReceiptRequest {
string provider_currency_code = 9; string provider_currency_code = 9;
string provider_pay_type = 10; string provider_pay_type = 10;
int64 provider_amount_minor = 11; int64 provider_amount_minor = 11;
// receive_address app-scoped SQL wallet YAML
// wallet-service admin
string receive_address = 12;
} }
message VerifyCoinSellerRechargeReceiptResponse { message VerifyCoinSellerRechargeReceiptResponse {
@ -2929,6 +2990,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 +3002,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"

View File

@ -0,0 +1,230 @@
# 幸运礼物外接接口对接文档
> 文档版本2026-07-15
> 接入方式服务端对服务端Server-to-Server
> 金额单位:对接 App 的金币最小整数单位
## 1. 接口用途
外部 App 在自己的账务系统完成幸运礼物扣费后,调用本接口获取一次最终抽奖结果。
平台负责:
- 按 `app_code` 隔离规则、奖池、RTP 和抽奖流水。
- 按 `app_code + request_id` 保证抽奖幂等。
- 返回最终 `reward_amount` 和倍率。
对接 App 负责:
- 调用前完成用户扣费。
- 成功收到结果后,在自己的账务系统中向用户发放 `reward_amount`
- 对超时或网络失败使用原 `request_id` 重试,不得重新生成业务请求 ID。
HyApp 不会操作外部 App 的用户钱包。`reward_status=granted` 表示抽奖结果已经生成并交给调用方,不表示奖励已在外部 App 入账。
## 2. 接口地址
| 类型 | 地址 |
|---|---|
| Base URL | `https://lucky-api.global-interaction.com` |
| 送礼接口 | `POST https://lucky-api.global-interaction.com/api/v1/lucky-gifts/send` |
| 就绪检查 | `GET https://lucky-api.global-interaction.com/healthz/ready` |
就绪检查:
```text
GET https://lucky-api.global-interaction.com/healthz/ready
```
2026-07-15 实测就绪检查返回 HTTP 200送礼请求可到达幸运礼物网关。
## 3. 调用前置条件
1. 向平台申请唯一 `app_code`,并加入幸运礼物网关白名单。
2. 确认该 App 的幸运礼物规则已启用。
3. 对接 App 必须先扣费,再调用抽奖接口。
4. 本接口只允许对接 App 服务端调用,禁止将请求能力下发到 App 或 Web 客户端。
当前版本的 HTTP 入口使用 `app_code` 白名单,尚未启用 Authorization Token 或请求签名,因此必须仅由已开通的对接 App 服务端调用。
## 4. 发送幸运礼物
### 4.1 请求
```http
POST /api/v1/lucky-gifts/send HTTP/1.1
Host: lucky-api.global-interaction.com
Content-Type: application/json
X-Request-Id: trace-20260715-000001
```
`X-Request-Id` 为可选的链路追踪 ID。如果不传平台会自动生成。它只用于日志排查不是抽奖幂等键。
### 4.2 请求体
```json
{
"app_code": "partner_app",
"request_id": "gift-order-20260715-000001",
"external_user_id": "user-10001",
"gift_count": 3,
"unit_amount": 100,
"currency": "COIN",
"metadata": {
"external_order_id": "order-1000001",
"gift_id": "lucky-rose",
"room_id": "room-20001"
}
}
```
### 4.3 字段说明
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---:|---|---|
| `app_code` | string | 是 | 去除首尾空格后 132 字符 | 平台分配的 App 编码;按小写存储和校验。 |
| `request_id` | string | 是 | 去除首尾空格后 1128 字符 | 对接 App 生成的业务幂等 ID建议直接使用送礼订单号或 UUID。 |
| `external_user_id` | string | 是 | 去除首尾空格后 1128 字符 | 用户在对接 App 中的稳定唯一 ID。 |
| `gift_count` | int64 | 是 | `> 0` | 本次已扣费的礼物数量。 |
| `unit_amount` | int64 | 是 | `> 0` | 单个礼物的金币整数价格。 |
| `currency` | string | 否 | 只支持 `COIN` | 缺省或空字符串时按 `COIN` 处理。 |
| `metadata` | object | 否 | JSON object | 对接方的展示或排查元数据;不参与金额、规则或幂等判定。 |
服务端按下式计算本次金额:
```text
total_amount = gift_count * unit_amount
```
请求体不得传入 `total_amount``pool_id``paid_at_ms`。奖池由平台规则选择,扣费完成时间由网关按 UTC 服务端时间写入。接口会拒绝所有未定义的顶层字段。
一个请求只返回一个聚合抽奖结果;`gift_count` 不会产生多个独立响应。
### 4.4 成功响应
HTTP Status`200 OK`
```json
{
"code": 0,
"message": "ok",
"request_id": "trace-20260715-000001",
"data": {
"draw_id": "external_lucky_draw_xxxxxxxxxx",
"request_id": "gift-order-20260715-000001",
"app_code": "partner_app",
"external_user_id": "user-10001",
"gift_count": 3,
"unit_amount": 100,
"total_amount": 300,
"reward_amount": 600,
"multiplier_ppm": 2000000,
"reward_status": "granted",
"rule_version": 1,
"created_at_ms": 1784073600000
}
}
```
### 4.5 响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` | int | 业务结果码;`0` 表示请求成功。 |
| `message` | string | 结果说明。 |
| 外层 `request_id` | string | HTTP 链路追踪 ID来自 `X-Request-Id` 或由平台生成。 |
| `data.draw_id` | string | 平台生成的唯一抽奖 ID对账时使用。 |
| `data.request_id` | string | 请求体中的业务幂等 ID。 |
| `data.app_code` | string | 归一化后的 App 编码。 |
| `data.external_user_id` | string | 对接 App 的用户 ID。 |
| `data.gift_count` | int64 | 礼物数量。 |
| `data.unit_amount` | int64 | 单件礼物金币价格。 |
| `data.total_amount` | int64 | 服务端计算的已扣费总额。 |
| `data.reward_amount` | int64 | 对接 App 应向用户发放的金币整数金额;以该字段为最终结算依据。 |
| `data.multiplier_ppm` | int64 | 倍率 ppm`1000000` 表示 1x`2000000` 表示 2x。 |
| `data.reward_status` | string | 当前为 `granted`,表示抽奖已完成且结果已交给对接 App 处理。 |
| `data.rule_version` | int64 | 本次抽奖命中的规则版本。 |
| `data.created_at_ms` | int64 | 抽奖结果创建时间Unix epoch millisecondsUTC。 |
未中奖时 `reward_amount``multiplier_ppm``0`。当前 JSON 序列化会省略数值为 `0` 的结果字段,对接方必须将缺失的 `reward_amount``multiplier_ppm``0` 处理,不得将字段缺失视为请求失败。
## 5. 幂等与重试
幂等键固定为:
```text
app_code + request_id
```
- 首次请求成功后,平台保存抽奖结果。
- 使用相同 `app_code + request_id` 重试时,直接返回首次结果,不会重新抽奖。
- 同一 `request_id` 不得对应不同用户、数量或金额。平台在幂等命中时会返回首次结果,不会用新参数覆盖。
- 超时、连接中断或 HTTP 5xx 时,使用原请求体和原 `request_id` 重试。
- 客户端超时建议设置为大于网关内部 3 秒处理超时,并对重试使用有上限的退避。
如果多次重试后仍无法确认结果,请保留 `app_code``request_id`、外层 `request_id` 和扣费流水,由平台根据抽奖记录对账。
## 6. 错误响应
### 6.1 统一格式
```json
{
"code": 40000,
"message": "request_id is required",
"request_id": "trace-20260715-000001"
}
```
### 6.2 错误码
| HTTP Status | `code` | 场景 | 对接方动作 |
|---:|---:|---|---|
| 400 | 40000 | JSON 无效、出现未定义字段、必填字段缺失、金额非正数或币种不支持 | 修正请求,不要原样重试。 |
| 403 | 40300 | `app_code` 未加入当前环境白名单 | 联系平台开通或检查环境。 |
| 404 | 无统一业务码 | 请求路径或 HTTP 方法错误 | 检查 Base URL、路径和请求方法。 |
| 500 | 50000 | 抽奖服务超时或内部处理失败 | 保持原 `request_id` 重试;不得重新扣费。 |
业务成功必须同时满足 HTTP Status 为 `200` 且响应 `code``0`
## 7. cURL 示例
```bash
curl --request POST \
'https://lucky-api.global-interaction.com/api/v1/lucky-gifts/send' \
--header 'Content-Type: application/json' \
--header 'X-Request-Id: trace-20260715-000001' \
--data '{
"app_code": "partner_app",
"request_id": "gift-order-20260715-000001",
"external_user_id": "user-10001",
"gift_count": 3,
"unit_amount": 100,
"currency": "COIN",
"metadata": {
"external_order_id": "order-1000001",
"gift_id": "lucky-rose",
"room_id": "room-20001"
}
}'
```
## 8. 标准处理流程
1. 对接 App 生成全局唯一的 `request_id`
2. 对接 App 在自有账务系统扣除 `gift_count * unit_amount`
3. 对接 App 调用幸运礼物接口。
4. 平台按 `app_code + request_id` 创建或返回已有抽奖结果。
5. 对接 App 在响应成功后按 `reward_amount` 入账,并保存 `draw_id`
6. 任何未知结果都使用原 `request_id` 重试;禁止重复扣费或重新生成抽奖业务号。
## 9. 联调检查清单
- [ ] `app_code` 已加入幸运礼物网关白名单。
- [ ] 规则已启用,并确认金币单位一致。
- [ ] 正常送礼可获得 HTTP 200 / `code=0`
- [ ] 重复发送相同 `request_id` 可获得相同 `draw_id` 和结果。
- [ ] 模拟超时后只重试抽奖请求,不重复扣费。
- [ ] 未中奖响应中缺失的数值字段可正确按 `0` 处理。
- [ ] 奖励按 `reward_amount` 在外部 App 入账,并持久化 `draw_id`
- [ ] 上线前完成小额真实扣费、抽奖、返奖和幂等重试验收。

File diff suppressed because it is too large Load Diff

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"
@ -351,10 +353,14 @@ func main() {
) )
luckyGiftHandler := luckygiftmodule.New(luckygiftadmin.NewGRPC(luckyGiftConn), cfg.LuckyGiftService.RequestTimeout, auditHandler) luckyGiftHandler := luckygiftmodule.New(luckygiftadmin.NewGRPC(luckyGiftConn), cfg.LuckyGiftService.RequestTimeout, auditHandler)
handlers := router.Handlers{ handlers := router.Handlers{
Audit: auditHandler, Audit: auditHandler,
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 idx_admin_app_config_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

@ -101,6 +101,31 @@ func (h *Handler) GetCoinSellerRechargeExchangeRate(c *gin.Context) {
response.OK(c, item) response.OK(c, item)
} }
func (h *Handler) GetUSDTAddresses(c *gin.Context) {
item, err := h.service.GetUSDTAddresses(shared.ActorFromContext(c), c.Param("app_code"))
if err != nil {
writeServiceError(c, err, "获取 USDT 收款地址失败")
return
}
response.OK(c, item)
}
func (h *Handler) UpsertUSDTAddress(c *gin.Context) {
var req financeUSDTAddressUpsertRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "USDT 收款地址参数不正确")
return
}
item, err := h.service.UpsertUSDTAddress(shared.ActorFromContext(c), c.Param("app_code"), c.Param("chain"), req)
if err != nil {
writeServiceError(c, err, "保存 USDT 收款地址失败")
return
}
// 收款地址决定真实资金凭证归属,审计资源 ID 同时包含 App 与链,便于后续定位误改来源。
shared.OperationLogWithResourceID(c, h.audit, "upsert-finance-usdt-address", "admin_app_configs", item.AppCode+":"+item.Chain, "success", "")
response.OK(c, item)
}
func (h *Handler) ReplaceCoinSellerRechargeExchangeRate(c *gin.Context) { func (h *Handler) ReplaceCoinSellerRechargeExchangeRate(c *gin.Context) {
var req coinSellerRechargeExchangeRatePolicyRequest var req coinSellerRechargeExchangeRatePolicyRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -17,6 +17,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
protected.POST("/admin/finance/orders/coin-seller-recharges", middleware.RequirePermission(permissionCoinSellerRechargeCreate), h.CreateCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges", middleware.RequirePermission(permissionCoinSellerRechargeCreate), h.CreateCoinSellerRechargeOrder)
protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/verify", middleware.RequirePermission(permissionCoinSellerRechargeVerify), h.VerifyCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/verify", middleware.RequirePermission(permissionCoinSellerRechargeVerify), h.VerifyCoinSellerRechargeOrder)
protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/grant", middleware.RequirePermission(permissionCoinSellerRechargeGrant), h.GrantCoinSellerRechargeOrder) protected.POST("/admin/finance/orders/coin-seller-recharges/:order_id/grant", middleware.RequirePermission(permissionCoinSellerRechargeGrant), h.GrantCoinSellerRechargeOrder)
protected.GET("/admin/finance/usdt-addresses/:app_code", middleware.RequirePermission(permissionCoinSellerRechargeView), h.GetUSDTAddresses)
protected.PUT("/admin/finance/usdt-addresses/:app_code/:chain", middleware.RequirePermission(permissionUSDTAddressUpdate), h.UpsertUSDTAddress)
protected.GET("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.GetCoinSellerRechargeExchangeRate) protected.GET("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.GetCoinSellerRechargeExchangeRate)
protected.PUT("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.ReplaceCoinSellerRechargeExchangeRate) protected.PUT("/admin/finance/coin-seller-recharge-exchange-rates/:app_code", middleware.RequirePermission(permissionCoinSellerExchangeRate), h.ReplaceCoinSellerRechargeExchangeRate)
} }

View File

@ -29,6 +29,7 @@ const (
permissionCoinSellerRechargeCreate = "finance-order:coin-seller-recharge:create" permissionCoinSellerRechargeCreate = "finance-order:coin-seller-recharge:create"
permissionCoinSellerRechargeVerify = "finance-order:coin-seller-recharge:verify" permissionCoinSellerRechargeVerify = "finance-order:coin-seller-recharge:verify"
permissionCoinSellerRechargeGrant = "finance-order:coin-seller-recharge:grant" permissionCoinSellerRechargeGrant = "finance-order:coin-seller-recharge:grant"
permissionUSDTAddressUpdate = "finance-order:usdt-address:update"
permissionCoinSellerExchangeRate = "coin-seller:exchange-rate" permissionCoinSellerExchangeRate = "coin-seller:exchange-rate"
coinSellerRechargeAssetType = "COIN_SELLER_COIN" coinSellerRechargeAssetType = "COIN_SELLER_COIN"
@ -261,6 +262,10 @@ func (s *Service) VerifyOrder(ctx context.Context, actor shared.Actor, id uint,
if order.GrantStatus == model.CoinSellerRechargeGrantStatusGranted { if order.GrantStatus == model.CoinSellerRechargeGrantStatusGranted {
return nil, repository.ErrCoinSellerRechargeOrderGranted return nil, repository.ErrCoinSellerRechargeOrderGranted
} }
receiveAddress, err := s.financeUSDTReceiveAddress(order.AppCode, order.Chain)
if err != nil {
return nil, err
}
resp, err := s.wallet.VerifyCoinSellerRechargeReceipt(appctx.WithContext(ctx, order.AppCode), &walletv1.VerifyCoinSellerRechargeReceiptRequest{ resp, err := s.wallet.VerifyCoinSellerRechargeReceipt(appctx.WithContext(ctx, order.AppCode), &walletv1.VerifyCoinSellerRechargeReceiptRequest{
RequestId: strings.TrimSpace(requestID), RequestId: strings.TrimSpace(requestID),
AppCode: order.AppCode, AppCode: order.AppCode,
@ -273,6 +278,7 @@ func (s *Service) VerifyOrder(ctx context.Context, actor shared.Actor, id uint,
ProviderCurrencyCode: order.ProviderCurrencyCode, ProviderCurrencyCode: order.ProviderCurrencyCode,
ProviderAmountMinor: order.ProviderAmountMinor, ProviderAmountMinor: order.ProviderAmountMinor,
ProviderPayType: order.ProviderPayType, ProviderPayType: order.ProviderPayType,
ReceiveAddress: receiveAddress,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -543,7 +549,12 @@ func (s *Service) verifyReceiptInput(ctx context.Context, input normalizedReceip
if s == nil || s.wallet == nil { if s == nil || s.wallet == nil {
return coinSellerRechargeReceiptVerificationDTO{}, errors.New("finance order wallet verifier is not configured") return coinSellerRechargeReceiptVerificationDTO{}, errors.New("finance order wallet verifier is not configured")
} }
// admin-server 不直连三方支付和链节点wallet-service 统一持有 provider 配置、收款地址和查单适配器。 receiveAddress, err := s.financeUSDTReceiveAddress(input.AppCode, input.Chain)
if err != nil {
return coinSellerRechargeReceiptVerificationDTO{}, err
}
// admin-server 不直连三方支付和链节点;它只把当前 App 的 SQL 地址作为可信配置快照传给 wallet-service
// wallet-service 继续统一持有 provider 凭证和查单适配器,避免任一服务反向读取对方数据库。
resp, err := s.wallet.VerifyCoinSellerRechargeReceipt(appctx.WithContext(ctx, input.AppCode), &walletv1.VerifyCoinSellerRechargeReceiptRequest{ resp, err := s.wallet.VerifyCoinSellerRechargeReceipt(appctx.WithContext(ctx, input.AppCode), &walletv1.VerifyCoinSellerRechargeReceiptRequest{
RequestId: strings.TrimSpace(requestID), RequestId: strings.TrimSpace(requestID),
AppCode: input.AppCode, AppCode: input.AppCode,
@ -556,6 +567,7 @@ func (s *Service) verifyReceiptInput(ctx context.Context, input normalizedReceip
ProviderCurrencyCode: input.ProviderCurrencyCode, ProviderCurrencyCode: input.ProviderCurrencyCode,
ProviderAmountMinor: input.ProviderAmountMinor, ProviderAmountMinor: input.ProviderAmountMinor,
ProviderPayType: input.ProviderPayType, ProviderPayType: input.ProviderPayType,
ReceiveAddress: receiveAddress,
}) })
if err != nil { if err != nil {
return coinSellerRechargeReceiptVerificationDTO{}, err return coinSellerRechargeReceiptVerificationDTO{}, err
@ -889,6 +901,8 @@ func normalizeRechargeProvider(providerCode string, chain string) (string, strin
return "usdt", "BSC", nil return "usdt", "BSC", nil
case "usdt_c2c", "c2c": case "usdt_c2c", "c2c":
return "usdt", "C2C", nil return "usdt", "C2C", nil
case "usdt_offchain", "offchain", "binance_offchain":
return "usdt", "OFFCHAIN", nil
default: default:
return "", "", errors.New("充值方式不正确") return "", "", errors.New("充值方式不正确")
} }
@ -906,6 +920,8 @@ func normalizeRechargeChain(chain string) string {
return "BSC" return "BSC"
case "c2c", "usdt_c2c": case "c2c", "usdt_c2c":
return "C2C" return "C2C"
case "offchain", "off_chain", "usdt_offchain", "binance_offchain":
return "OFFCHAIN"
default: default:
return "" return ""
} }
@ -922,6 +938,8 @@ func receiptProviderCode(order model.CoinSellerRechargeOrder) string {
return "usdt_bep20" return "usdt_bep20"
case "C2C": case "C2C":
return "usdt_c2c" return "usdt_c2c"
case "OFFCHAIN":
return "usdt_offchain"
default: default:
return "usdt" return "usdt"
} }
@ -948,6 +966,8 @@ func receiptChain(order model.CoinSellerRechargeOrder) string {
return "bep20" return "bep20"
case "C2C": case "C2C":
return "c2c" return "c2c"
case "OFFCHAIN":
return "offchain"
default: default:
return "" return ""
} }

View File

@ -38,6 +38,23 @@ func TestNormalizeCreateOrderRequiresExplicitUSDTChain(t *testing.T) {
} }
} }
func TestNormalizeCreateOrderSupportsExplicitBinanceOffchain(t *testing.T) {
input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{
AppCode: "aslan",
TargetUserID: "4337389",
USDAmount: 300.14,
ProviderCode: "usdt",
Chain: "binance_offchain",
ExternalOrderNo: "391252658053",
})
if err != nil {
t.Fatalf("normalize explicit Binance Off-chain failed: %v", err)
}
if input.ProviderCode != "usdt" || input.Chain != "OFFCHAIN" || input.USDMinorAmount != 30014 || receiptVerificationProviderCode(normalizedReceiptVerificationInput{ProviderCode: input.ProviderCode, Chain: input.Chain}) != "usdt_offchain" {
t.Fatalf("explicit Binance Off-chain mapping mismatch: %+v", input)
}
}
func TestNormalizeCreateOrderRequiresProviderCurrencyAmount(t *testing.T) { func TestNormalizeCreateOrderRequiresProviderCurrencyAmount(t *testing.T) {
for _, providerCode := range []string{"mifapay", "v5pay"} { for _, providerCode := range []string{"mifapay", "v5pay"} {
_, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{ _, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{

View File

@ -0,0 +1,198 @@
package financeorder
import (
"errors"
"fmt"
"sort"
"strings"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/model"
"hyapp-admin-server/internal/modules/shared"
"gorm.io/gorm"
)
const financeUSDTAddressGroup = "finance-usdt-address"
var financeUSDTChainOrder = map[string]int{
"TRON": 0,
"BSC": 1,
}
type financeUSDTAddressDTO struct {
Chain string `json:"chain"`
Address string `json:"address"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
type financeUSDTAddressesDTO struct {
AppCode string `json:"appCode"`
Items []financeUSDTAddressDTO `json:"items"`
}
type financeUSDTAddressUpsertRequest struct {
Address string `json:"address"`
}
type financeUSDTAddressUpsertDTO struct {
AppCode string `json:"appCode"`
Chain string `json:"chain"`
Address string `json:"address"`
UpdatedAtMS int64 `json:"updatedAtMs"`
}
// GetUSDTAddresses 只读取当前 App 自己持有的配置行,不继承空 app_code 的历史基线。
// 收款地址一旦跨 App 回退,运营会拿到另一账套的地址并让链上订单校验必然失败。
func (s *Service) GetUSDTAddresses(actor shared.Actor, appCode string) (*financeUSDTAddressesDTO, error) {
if s == nil || s.store == nil {
return nil, errors.New("admin store is not configured")
}
if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionCoinSellerRechargeView) {
return nil, errCoinSellerRechargeForbidden
}
if strings.TrimSpace(appCode) == "" {
return nil, errInvalidCoinSellerRechargeOrderInput
}
appCode = appctx.Normalize(appCode)
// App 范围是后台用户的数据边界;即使调用方绕过页面手写 app_code也不能读取未授权账套地址。
access, err := s.store.AppAccessForUser(actor.UserID)
if err != nil {
return nil, err
}
if !access.Allows(appCode) {
return nil, errCoinSellerRechargeForbidden
}
configs, err := s.store.ListOwnedAppConfigs(appCode, financeUSDTAddressGroup)
if err != nil {
return nil, err
}
items, err := financeUSDTAddressesFromConfigs(configs)
if err != nil {
return nil, err
}
return &financeUSDTAddressesDTO{AppCode: appCode, Items: items}, nil
}
// UpsertUSDTAddress 把收款地址写入现有 app-scoped 配置表;唯一键确保更新是单行 upsert
// 不新增地址历史副本,也不会覆盖其他 App。生产查单会在每次校验前重新读取该行。
func (s *Service) UpsertUSDTAddress(actor shared.Actor, appCode string, chain string, req financeUSDTAddressUpsertRequest) (*financeUSDTAddressUpsertDTO, error) {
if s == nil || s.store == nil {
return nil, errors.New("admin store is not configured")
}
if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionUSDTAddressUpdate) {
return nil, errCoinSellerRechargeForbidden
}
if strings.TrimSpace(appCode) == "" {
return nil, errInvalidCoinSellerRechargeOrderInput
}
appCode = appctx.Normalize(appCode)
chain = strings.ToUpper(strings.TrimSpace(chain))
address := strings.TrimSpace(req.Address)
if _, supported := financeUSDTChainOrder[chain]; !supported {
return nil, errors.New("USDT 链不正确")
}
if !validFinanceUSDTAddress(chain, address) {
return nil, errors.New("USDT 收款地址不正确")
}
access, err := s.store.AppAccessForUser(actor.UserID)
if err != nil {
return nil, err
}
if !access.Allows(appCode) {
return nil, errCoinSellerRechargeForbidden
}
if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{
Group: financeUSDTAddressGroup,
Key: chain,
Value: address,
Description: "财务 USDT 收款地址",
}}); err != nil {
return nil, err
}
item, err := s.store.GetOwnedAppConfig(appCode, financeUSDTAddressGroup, chain)
if err != nil {
return nil, err
}
return &financeUSDTAddressUpsertDTO{AppCode: appCode, Chain: chain, Address: item.Value, UpdatedAtMS: item.UpdatedAtMS}, nil
}
// financeUSDTReceiveAddress 把订单校验链映射到当前 SQL 地址。Off-chain 虽没有公链 hash
// 但 Binance Deposit History 仍记录实际 TRX 收款地址,因此必须复用 TRON 配置C2C Pay 则没有地址。
func (s *Service) financeUSDTReceiveAddress(appCode string, receiptChain string) (string, error) {
addressChain := ""
switch strings.ToUpper(strings.TrimSpace(receiptChain)) {
case "TRON":
addressChain = "TRON"
case "BSC":
addressChain = "BSC"
case "OFFCHAIN":
addressChain = "TRON"
case "", "C2C":
return "", nil
default:
return "", errors.New("USDT 链不正确")
}
item, err := s.store.GetOwnedAppConfig(appctx.Normalize(appCode), financeUSDTAddressGroup, addressChain)
if errors.Is(err, gorm.ErrRecordNotFound) {
return "", fmt.Errorf("当前 APP 未配置 %s USDT 收款地址", addressChain)
}
if err != nil {
return "", err
}
address := strings.TrimSpace(item.Value)
if !validFinanceUSDTAddress(addressChain, address) {
return "", fmt.Errorf("%s USDT 收款地址配置不正确", addressChain)
}
return address, nil
}
func financeUSDTAddressesFromConfigs(configs []model.AppConfig) ([]financeUSDTAddressDTO, error) {
items := make([]financeUSDTAddressDTO, 0, len(configs))
for _, config := range configs {
chain := strings.ToUpper(strings.TrimSpace(config.Key))
if _, supported := financeUSDTChainOrder[chain]; !supported {
// 同组未来若新增其他链,旧版本必须安全忽略,不能把未知地址误标成 TRON/BSC。
continue
}
address := strings.TrimSpace(config.Value)
if address == "" {
continue
}
if !validFinanceUSDTAddress(chain, address) {
return nil, fmt.Errorf("%s USDT 收款地址配置不正确", chain)
}
items = append(items, financeUSDTAddressDTO{Chain: chain, Address: address, UpdatedAtMS: config.UpdatedAtMS})
}
sort.Slice(items, func(i, j int) bool {
return financeUSDTChainOrder[items[i].Chain] < financeUSDTChainOrder[items[j].Chain]
})
return items, nil
}
func validFinanceUSDTAddress(chain string, address string) bool {
switch chain {
case "TRON":
if len(address) != 34 || address[0] != 'T' {
return false
}
for _, char := range address {
if !strings.ContainsRune("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", char) {
return false
}
}
return true
case "BSC":
if len(address) != 42 || !strings.HasPrefix(address, "0x") {
return false
}
for _, char := range address[2:] {
if !strings.ContainsRune("0123456789abcdefABCDEF", char) {
return false
}
}
return true
default:
return false
}
}

View File

@ -0,0 +1,62 @@
package financeorder
import (
"testing"
"hyapp-admin-server/internal/model"
"hyapp-admin-server/internal/repository"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestFinanceUSDTAddressesFromConfigsKeepsSupportedValidChainsInBusinessOrder(t *testing.T) {
items, err := financeUSDTAddressesFromConfigs([]model.AppConfig{
{Key: "BSC", Value: "0x55d398326f99059fF775485246999027B3197955", UpdatedAtMS: 200},
{Key: "future-chain", Value: "not-exposed", UpdatedAtMS: 300},
{Key: "TRON", Value: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", UpdatedAtMS: 100},
})
if err != nil {
t.Fatalf("normalize USDT address configs failed: %v", err)
}
if len(items) != 2 || items[0].Chain != "TRON" || items[1].Chain != "BSC" {
t.Fatalf("USDT addresses must expose only supported chains in business order: %+v", items)
}
}
func TestFinanceUSDTAddressesFromConfigsRejectsMalformedStoredAddress(t *testing.T) {
_, err := financeUSDTAddressesFromConfigs([]model.AppConfig{{Key: "TRON", Value: "wrong-app-address"}})
if err == nil {
t.Fatal("malformed SQL address must fail closed instead of being copied into a recharge order")
}
}
func TestFinanceUSDTReceiveAddressMapsOffchainToCurrentTRONSQLRow(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sql mock failed: %v", err)
}
defer func() { _ = sqlDB.Close() }()
gormDB, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
if err != nil {
t.Fatalf("create gorm db failed: %v", err)
}
const address = "TLv7wERsM42ZeobMrrKVC5C72d3gFUa44J"
mock.ExpectQuery("SELECT \\* FROM `admin_app_configs` WHERE app_code = \\? AND `group` = \\? AND `key` = \\? AND is_deleted = FALSE").
WithArgs("aslan", financeUSDTAddressGroup, "TRON", 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "app_code", "group", "key", "value", "description", "is_deleted", "created_at_ms", "updated_at_ms"}).
AddRow(9, "aslan", financeUSDTAddressGroup, "TRON", address, "财务 USDT 收款地址", false, int64(100), int64(200)))
service := NewService(repository.New(gormDB), nil, nil)
got, err := service.financeUSDTReceiveAddress(" ASLAN ", "OFFCHAIN")
if err != nil {
t.Fatalf("load current Off-chain TRON address failed: %v", err)
}
if got != address {
t.Fatalf("Off-chain must use current app-scoped TRON SQL address, got %q", got)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("sql expectations mismatch: %v", err)
}
}

View File

@ -31,6 +31,7 @@ var (
"finance-order:coin-seller-recharge:create", "finance-order:coin-seller-recharge:create",
"finance-order:coin-seller-recharge:verify", "finance-order:coin-seller-recharge:verify",
"finance-order:coin-seller-recharge:grant", "finance-order:coin-seller-recharge:grant",
"finance-order:usdt-address:update",
"finance-withdrawal:view", "finance-withdrawal:view",
"finance-withdrawal:audit", "finance-withdrawal:audit",
} }
@ -124,12 +125,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

@ -142,6 +142,7 @@ var defaultPermissions = []model.Permission{
{Name: "币商充值订单创建", Code: "finance-order:coin-seller-recharge:create", Kind: "button"}, {Name: "币商充值订单创建", Code: "finance-order:coin-seller-recharge:create", Kind: "button"},
{Name: "币商充值订单校验", Code: "finance-order:coin-seller-recharge:verify", Kind: "button"}, {Name: "币商充值订单校验", Code: "finance-order:coin-seller-recharge:verify", Kind: "button"},
{Name: "币商充值订单发放", Code: "finance-order:coin-seller-recharge:grant", Kind: "button"}, {Name: "币商充值订单发放", Code: "finance-order:coin-seller-recharge:grant", Kind: "button"},
{Name: "USDT 收款地址编辑", Code: "finance-order:usdt-address:update", Kind: "button", Description: "允许按 APP 新增或修改 USDT 收款地址"},
{Name: "用户提现申请查看", Code: "finance-withdrawal:view", Kind: "menu"}, {Name: "用户提现申请查看", Code: "finance-withdrawal:view", Kind: "menu"},
{Name: "用户提现申请审核", Code: "finance-withdrawal:audit", Kind: "button"}, {Name: "用户提现申请审核", Code: "finance-withdrawal:audit", Kind: "button"},
{Name: "内购配置查看", Code: "payment-product:view", Kind: "menu"}, {Name: "内购配置查看", Code: "payment-product:view", Kind: "menu"},
@ -173,6 +174,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 +279,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 +370,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)
@ -185,7 +188,9 @@ func New(cfg config.Config, auth *service.AuthService, store *repository.Store,
prettyid.RegisterRoutes(appProtected, h.PrettyID) prettyid.RegisterRoutes(appProtected, h.PrettyID)
job.RegisterRoutes(protected, h.Job) job.RegisterRoutes(protected, h.Job)
levelconfig.RegisterRoutes(appProtected, h.LevelConfig) levelconfig.RegisterRoutes(appProtected, h.LevelConfig)
opscenter.RegisterRoutes(appProtected, h.OpsCenter) // Ops Center 是跨 App 工作台:它必须先读取应用目录,再按 app_code 扇出查询。
// 这里仅沿用登录、审计和模块内 lucky-gift 权限,不能要求尚未选出的 X-App-Code。
opscenter.RegisterRoutes(protected, h.OpsCenter)
upload.RegisterRoutes(appProtected, h.Upload) upload.RegisterRoutes(appProtected, h.Upload)
search.RegisterRoutes(protected, h.Search) search.RegisterRoutes(protected, h.Search)
sevendaycheckin.RegisterRoutes(appProtected, h.SevenDayCheckIn) sevendaycheckin.RegisterRoutes(appProtected, h.SevenDayCheckIn)

View File

@ -0,0 +1,46 @@
package router
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"hyapp-admin-server/internal/config"
"hyapp-admin-server/internal/modules/opscenter"
"hyapp-admin-server/internal/service"
"github.com/gin-gonic/gin"
)
func TestOpsCenterAppBootstrapDoesNotRequireAppCode(t *testing.T) {
gin.SetMode(gin.TestMode)
auth := service.NewAuthService("ops-center-router-test-secret", time.Hour)
token, _, err := auth.GenerateAccessToken(7, "operator", nil)
if err != nil {
t.Fatalf("generate access token: %v", err)
}
engine := New(config.Config{}, auth, nil, Handlers{OpsCenter: opscenter.New(nil, nil)})
request := httptest.NewRequest(http.MethodGet, "/api/v1/admin/ops-center/apps", nil)
request.Header.Set("Authorization", "Bearer "+token)
response := httptest.NewRecorder()
engine.ServeHTTP(response, request)
// Ops Center 先读取跨 App 目录,再按 app_code 查询每个应用的数据;启动接口若要求
// X-App-Code就会形成“先拿到 App 才能发 header、先发 header 才能拿 App”的死锁。
if response.Code != http.StatusOK {
t.Fatalf("status = %d body=%s", response.Code, response.Body.String())
}
if body := response.Body.String(); !strings.Contains(body, `"app_code":"yumi"`) || !strings.Contains(body, `"app_code":"aslan"`) {
t.Fatalf("bootstrap app catalog missing fixed apps: %s", body)
}
// 从 appProtected 移出只解除 App 选择依赖;路由仍必须位于 protected 下,不能变成匿名应用目录。
unauthorized := httptest.NewRecorder()
engine.ServeHTTP(unauthorized, httptest.NewRequest(http.MethodGet, "/api/v1/admin/ops-center/apps", nil))
if unauthorized.Code != http.StatusUnauthorized {
t.Fatalf("unauthorized status = %d body=%s", unauthorized.Code, unauthorized.Body.String())
}
}

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 idx_admin_app_config_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

@ -95,7 +95,7 @@ JOIN admin_permissions permission
WHERE role.code = 'ops-admin' WHERE role.code = 'ops-admin'
AND permission.code IN ( AND permission.code IN (
'overview:view', 'overview:view',
'finance-order:coin-seller-recharge:view', 'finance-order:coin-seller-recharge:create', 'finance-order:coin-seller-recharge:verify', 'finance-order:coin-seller-recharge:grant', 'finance-order:coin-seller-recharge:view', 'finance-order:coin-seller-recharge:create', 'finance-order:coin-seller-recharge:verify', 'finance-order:coin-seller-recharge:grant', 'finance-order:usdt-address:update',
'finance-withdrawal:view', 'finance-withdrawal:audit', 'finance-withdrawal:view', 'finance-withdrawal:audit',
'app-user:view', 'app-user:export', 'app-user:update', 'app-user:level', 'app-user:status', 'app-user:password', 'app-user:view', 'app-user:export', 'app-user:update', 'app-user:level', 'app-user:status', 'app-user:password',
'level-config:view', 'level-config:update', 'pretty-id:view', 'pretty-id:update', 'pretty-id:grant', 'level-config:view', 'level-config:update', 'pretty-id:view', 'pretty-id:update', 'pretty-id:grant',
@ -146,7 +146,7 @@ JOIN admin_permissions permission
WHERE role.code = 'operations-specialist' WHERE role.code = 'operations-specialist'
AND permission.code IN ( AND permission.code IN (
'overview:view', 'overview:view',
'finance-order:coin-seller-recharge:view', 'finance-order:coin-seller-recharge:create', 'finance-order:coin-seller-recharge:verify', 'finance-order:coin-seller-recharge:grant', 'finance-order:coin-seller-recharge:view', 'finance-order:coin-seller-recharge:create', 'finance-order:coin-seller-recharge:verify', 'finance-order:coin-seller-recharge:grant', 'finance-order:usdt-address:update',
'finance-withdrawal:view', 'finance-withdrawal:audit', 'finance-withdrawal:view', 'finance-withdrawal:audit',
'app-user:view', 'app-user:export', 'app-user:update', 'app-user:level', 'app-user:status', 'app-user:password', 'app-user:view', 'app-user:export', 'app-user:update', 'app-user:level', 'app-user:status', 'app-user:password',
'level-config:view', 'level-config:update', 'pretty-id:view', 'pretty-id:update', 'pretty-id:grant', 'level-config:view', 'level-config:update', 'pretty-id:view', 'pretty-id:update', 'pretty-id:grant',

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

@ -0,0 +1,19 @@
-- 收款地址是按 APP 隔离的财务敏感配置;独立写权限避免仅有订单查看权限的账号修改校验口径。
-- 迁移只通过角色 code、权限 code 唯一索引写少量元数据,不扫描订单或配置业务表。
SET @now_ms = CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED);
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms)
VALUES ('USDT 收款地址编辑', 'finance-order:usdt-address:update', 'button', '允许按 APP 新增或修改 USDT 收款地址', @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
kind = VALUES(kind),
description = VALUES(description),
updated_at_ms = @now_ms;
-- 平台管理员、运营负责人和运营专员原本都能创建并校验币商充值订单;地址维护沿用同一岗位边界。
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT role.id, permission.id
FROM admin_roles role
JOIN admin_permissions permission
WHERE role.code IN ('platform-admin', 'ops-admin', 'operations-specialist')
AND permission.code = 'finance-order:usdt-address:update';

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"
@ -28,32 +29,37 @@ import (
// App 装配 activity-service 的 gRPC 入口、健康检查、后台 worker 和外部连接。 // App 装配 activity-service 的 gRPC 入口、健康检查、后台 worker 和外部连接。
// 具体业务模块的创建和注册都放在独立文件里,避免启动入口继续膨胀成一个难维护的大函数。 // 具体业务模块的创建和注册都放在独立文件里,避免启动入口继续膨胀成一个难维护的大函数。
type App struct { type App struct {
server *grpc.Server server *grpc.Server
listener net.Listener listener net.Listener
health *grpchealth.ServingChecker health *grpchealth.ServingChecker
healthHTTP *healthhttp.Server healthHTTP *healthhttp.Server
mysqlRepo *mysqlstorage.Repository mysqlRepo *mysqlstorage.Repository
broadcast *broadcastservice.Service broadcast *broadcastservice.Service
firstRechargeReward *firstrechargeservice.Service firstRechargeReward *firstrechargeservice.Service
cumulativeRecharge *cumulativerechargeservice.Service cumulativeRecharge *cumulativerechargeservice.Service
inviteActivityReward *inviteactivityservice.Service inviteActivityReward *inviteactivityservice.Service
actionConfirm *messageservice.ActionConfirmService actionConfirm *messageservice.ActionConfirmService
wheel *wheelservice.Service wheel *wheelservice.Service
broadcastWorkerEnabled bool activityTemplate *activitytemplateservice.Service
messageActionWorkerEnabled bool broadcastWorkerEnabled bool
wheelRewardWorkerEnabled bool messageActionWorkerEnabled bool
workerNodeID string wheelRewardWorkerEnabled bool
messageActionWorkerOptions config.MessageActionConfirmWorkerConfig activityTemplateStatsWorkerEnabled bool
wheelRewardWorkerOptions config.WheelRewardWorkerConfig workerNodeID string
messageActionOutboxTopic string messageActionWorkerOptions config.MessageActionConfirmWorkerConfig
userLeaderboardRedisClose func() error wheelRewardWorkerOptions config.WheelRewardWorkerConfig
mqConsumers []*rocketmqx.Consumer activityTemplateStatsWorkerOptions config.ActivityTemplateStatsWorkerConfig
messageActionProducer *rocketmqx.Producer messageActionOutboxTopic string
userConn *grpc.ClientConn activityTemplateOutboxTopic string
walletConn *grpc.ClientConn userLeaderboardRedisClose func() error
roomConn *grpc.ClientConn mqConsumers []*rocketmqx.Consumer
workers *serviceapp.BackgroundGroup messageActionProducer *rocketmqx.Producer
closeOnce sync.Once activityTemplateProducer *rocketmqx.Producer
userConn *grpc.ClientConn
walletConn *grpc.ClientConn
roomConn *grpc.ClientConn
workers *serviceapp.BackgroundGroup
closeOnce sync.Once
} }
// New 初始化 activity-service 应用。 // New 初始化 activity-service 应用。
@ -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()
@ -127,31 +141,36 @@ func New(cfg config.Config) (*App, error) {
} }
return &App{ return &App{
server: server, server: server,
listener: listener, listener: listener,
health: health, health: health,
healthHTTP: healthHTTP, healthHTTP: healthHTTP,
mysqlRepo: repository, mysqlRepo: repository,
broadcast: services.broadcast, broadcast: services.broadcast,
firstRechargeReward: services.firstRechargeReward, firstRechargeReward: services.firstRechargeReward,
cumulativeRecharge: services.cumulativeRecharge, cumulativeRecharge: services.cumulativeRecharge,
inviteActivityReward: services.inviteActivityReward, inviteActivityReward: services.inviteActivityReward,
actionConfirm: services.actionConfirm, actionConfirm: services.actionConfirm,
wheel: services.wheel, wheel: services.wheel,
broadcastWorkerEnabled: cfg.Broadcast.Enabled, activityTemplate: services.activityTemplate,
messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled, broadcastWorkerEnabled: cfg.Broadcast.Enabled,
wheelRewardWorkerEnabled: cfg.WheelRewardWorker.Enabled, messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled,
workerNodeID: cfg.NodeID, wheelRewardWorkerEnabled: cfg.WheelRewardWorker.Enabled,
messageActionWorkerOptions: cfg.MessageActionConfirmWorker, activityTemplateStatsWorkerEnabled: cfg.ActivityTemplateStatsWorker.Enabled,
wheelRewardWorkerOptions: cfg.WheelRewardWorker, workerNodeID: cfg.NodeID,
messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic, messageActionWorkerOptions: cfg.MessageActionConfirmWorker,
userLeaderboardRedisClose: userLeaderboardRedisClose, wheelRewardWorkerOptions: cfg.WheelRewardWorker,
mqConsumers: mqConsumers, activityTemplateStatsWorkerOptions: cfg.ActivityTemplateStatsWorker,
messageActionProducer: messageActionProducer, messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic,
userConn: clients.userConn, activityTemplateOutboxTopic: cfg.RocketMQ.ActivityTemplateOutbox.Topic,
walletConn: clients.walletConn, userLeaderboardRedisClose: userLeaderboardRedisClose,
roomConn: clients.roomConn, mqConsumers: mqConsumers,
workers: serviceapp.NewBackground(context.Background()), messageActionProducer: messageActionProducer,
activityTemplateProducer: activityTemplateProducer,
userConn: clients.userConn,
walletConn: clients.walletConn,
roomConn: clients.roomConn,
workers: serviceapp.NewBackground(context.Background()),
}, nil }, nil
} }
@ -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"`
@ -182,27 +197,29 @@ type BroadcastConfig struct {
// RocketMQConfig 描述 activity-service 消费房间事实的 MQ 连接。 // RocketMQConfig 描述 activity-service 消费房间事实的 MQ 连接。
type RocketMQConfig struct { type RocketMQConfig struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
NameServers []string `yaml:"name_servers"` NameServers []string `yaml:"name_servers"`
NameServerDomain string `yaml:"name_server_domain"` NameServerDomain string `yaml:"name_server_domain"`
AccessKey string `yaml:"access_key"` AccessKey string `yaml:"access_key"`
SecretKey string `yaml:"secret_key"` SecretKey string `yaml:"secret_key"`
SecurityToken string `yaml:"security_token"` SecurityToken string `yaml:"security_token"`
Namespace string `yaml:"namespace"` Namespace string `yaml:"namespace"`
VIPChannel bool `yaml:"vip_channel"` VIPChannel bool `yaml:"vip_channel"`
RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"` RoomOutbox RoomOutboxMQConfig `yaml:"room_outbox"`
LuckyGiftOutbox LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"` LuckyGiftOutbox LuckyGiftOutboxMQConfig `yaml:"lucky_gift_outbox"`
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 的消费位点。
type RoomOutboxMQConfig struct { 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"`
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` ActivityTemplateConsumerGroup string `yaml:"activity_template_consumer_group"`
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
} }
// LuckyGiftOutboxMQConfig 控制 activity 对幸运礼物 owner fact 的独立消费位点。 // LuckyGiftOutboxMQConfig 控制 activity 对幸运礼物 owner fact 的独立消费位点。
@ -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,
@ -335,10 +364,11 @@ func defaultRocketMQConfig() RocketMQConfig {
return RocketMQConfig{ return RocketMQConfig{
Enabled: false, Enabled: false,
RoomOutbox: RoomOutboxMQConfig{ RoomOutbox: RoomOutboxMQConfig{
Enabled: false, Enabled: false,
Topic: "hyapp_room_outbox", Topic: "hyapp_room_outbox",
ConsumerGroup: "hyapp-activity-room-outbox", ConsumerGroup: "hyapp-activity-room-outbox",
ConsumerMaxReconsumeTimes: 16, ActivityTemplateConsumerGroup: "hyapp-activity-template-room-outbox",
ConsumerMaxReconsumeTimes: 16,
}, },
LuckyGiftOutbox: LuckyGiftOutboxMQConfig{ LuckyGiftOutbox: LuckyGiftOutboxMQConfig{
Enabled: false, Enabled: false,
@ -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

@ -16,7 +16,27 @@ 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"
@ -21,25 +22,29 @@ type BroadcastServer struct {
activityv1.UnimplementedBroadcastServiceServer activityv1.UnimplementedBroadcastServiceServer
activityv1.UnimplementedRoomEventConsumerServiceServer activityv1.UnimplementedRoomEventConsumerServiceServer
svc *broadcastservice.Service svc *broadcastservice.Service
growth *growthservice.Service growth *growthservice.Service
task *taskservice.Service task *taskservice.Service
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"
@ -20,17 +21,18 @@ import (
type CronServer struct { type CronServer struct {
activityv1.UnimplementedActivityCronServiceServer activityv1.UnimplementedActivityCronServiceServer
message *messageservice.Service message *messageservice.Service
growth *growthservice.Service growth *growthservice.Service
achievement *achievementservice.Service achievement *achievementservice.Service
weeklyStar *weeklystarservice.Service weeklyStar *weeklystarservice.Service
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

@ -57,6 +57,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -57,6 +57,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -57,6 +57,12 @@ tasks:
timeout: "20s" timeout: "20s"
lock_ttl: "1m" lock_ttl: "1m"
batch_size: 50 batch_size: 50
activity_template_settlement:
enabled: true
interval: "5s"
timeout: "30s"
lock_ttl: "1m"
batch_size: 50
agency_opening_settlement: agency_opening_settlement:
enabled: true enabled: true
interval: "5s" interval: "5s"

View File

@ -115,12 +115,12 @@ func New(cfg config.Config) (*App, error) {
gameCron := integration.NewGameCronClient(gamev1.NewGameCronServiceClient(gameConn)) gameCron := integration.NewGameCronClient(gamev1.NewGameCronServiceClient(gameConn))
walletCron := integration.NewWalletCronClient(walletv1.NewWalletCronServiceClient(walletConn)) walletCron := integration.NewWalletCronClient(walletv1.NewWalletCronServiceClient(walletConn))
taskScheduler := scheduler.New(cfg.NodeID, cfg.Tasks, repository, map[string]scheduler.Handler{ taskScheduler := scheduler.New(cfg.NodeID, cfg.Tasks, repository, map[string]scheduler.Handler{
"login_ip_risk": userCron.ProcessLoginIPRiskBatch, "login_ip_risk": userCron.ProcessLoginIPRiskBatch,
"user_region_rebuild": userCron.ProcessRegionRebuildBatch, "user_region_rebuild": userCron.ProcessRegionRebuildBatch,
"mic_open_session_compensation": userCron.CompensateMicOpenSessions, "mic_open_session_compensation": userCron.CompensateMicOpenSessions,
"room_open_session_compensation": userCron.CompensateRoomOpenSessions, "room_open_session_compensation": userCron.CompensateRoomOpenSessions,
"manager_user_block_expiry": userCron.ExpireManagerUserBlocks, "manager_user_block_expiry": userCron.ExpireManagerUserBlocks,
"admin_user_ban_expiry": userCron.ExpireAdminUserBans, "admin_user_ban_expiry": userCron.ExpireAdminUserBans,
// CP 榜单任务只占 cron 租约并调用 user-service真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。 // CP 榜单任务只占 cron 租约并调用 user-service真实 MySQL 读取、wallet 头像框补齐和 Redis 替换都在 user-service 内完成。
"cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard, "cp_intimacy_leaderboard": userCron.RefreshCPIntimacyLeaderboard,
"message_fanout": activityCron.ProcessMessageFanoutBatch, "message_fanout": activityCron.ProcessMessageFanoutBatch,
@ -128,6 +128,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

@ -179,6 +179,13 @@ func defaultTasks() map[string]TaskConfig {
LockTTL: "1m", LockTTL: "1m",
BatchSize: 50, BatchSize: 50,
}, },
"activity_template_settlement": {
Enabled: true,
Interval: "5s",
Timeout: "30s",
LockTTL: "1m",
BatchSize: 50,
},
"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)
}

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