Compare commits
31 Commits
f453ede08b
...
bf2735d068
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2735d068 | ||
|
|
35060cc1fc | ||
|
|
72ec2bfcc9 | ||
|
|
e385704a7d | ||
|
|
a93c3f24d1 | ||
|
|
7cd08d0967 | ||
|
|
9b4447e158 | ||
|
|
13f07d800f | ||
|
|
d57419c99e | ||
|
|
1e3d628f57 | ||
|
|
f8e34770d3 | ||
|
|
4028928123 | ||
|
|
6fd0f03895 | ||
|
|
9c547faf0e | ||
|
|
9a19267c6a | ||
|
|
b97e5f0011 | ||
|
|
ac6672f398 | ||
|
|
9c39d33bf2 | ||
|
|
dba6035fbe | ||
|
|
93250e5a7b | ||
|
|
059de783ac | ||
|
|
d51b735783 | ||
|
|
d947ebbfa3 | ||
|
|
3cf62395e0 | ||
|
|
ac767d1c7e | ||
|
|
f455df9192 | ||
|
|
1ac0cecf63 | ||
|
|
395470f3ba | ||
|
|
ebc99a097d | ||
|
|
6cfe45f73a | ||
|
|
ee86af4c14 |
File diff suppressed because it is too large
Load Diff
@ -178,6 +178,67 @@ message CreateFanoutJobResponse {
|
|||||||
bool created = 3;
|
bool created = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionConfirm 是 App C2C IM 操作按钮背后的后端确认事实。
|
||||||
|
message ActionConfirm {
|
||||||
|
string confirm_id = 1;
|
||||||
|
string business_type = 2;
|
||||||
|
string business_subtype = 3;
|
||||||
|
string business_id = 4;
|
||||||
|
int64 sender_user_id = 5;
|
||||||
|
int64 receiver_user_id = 6;
|
||||||
|
string status = 7;
|
||||||
|
string action = 8;
|
||||||
|
int64 processed_at_ms = 9;
|
||||||
|
int64 created_at_ms = 10;
|
||||||
|
int64 updated_at_ms = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AcceptActionConfirmRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string confirm_id = 2;
|
||||||
|
int64 actor_user_id = 3;
|
||||||
|
string command_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AcceptActionConfirmResponse {
|
||||||
|
ActionConfirm confirm = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RejectActionConfirmRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string confirm_id = 2;
|
||||||
|
int64 actor_user_id = 3;
|
||||||
|
string command_id = 4;
|
||||||
|
string reason = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RejectActionConfirmResponse {
|
||||||
|
ActionConfirm confirm = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BatchGetActionConfirmStatusRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
int64 user_id = 2;
|
||||||
|
repeated string confirm_ids = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BatchGetActionConfirmStatusResponse {
|
||||||
|
repeated ActionConfirm items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListConversationActionConfirmsRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
int64 user_id = 2;
|
||||||
|
int64 peer_user_id = 3;
|
||||||
|
int32 page_size = 4;
|
||||||
|
string page_token = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListConversationActionConfirmsResponse {
|
||||||
|
repeated ActionConfirm items = 1;
|
||||||
|
string next_page_token = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// CronBatchRequest 是 cron-service 触发 activity-service 后台批处理的统一请求。
|
// CronBatchRequest 是 cron-service 触发 activity-service 后台批处理的统一请求。
|
||||||
message CronBatchRequest {
|
message CronBatchRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
@ -2256,6 +2317,7 @@ message GetCPWeeklyRankStatusResponse {
|
|||||||
int64 period_start_ms = 3;
|
int64 period_start_ms = 3;
|
||||||
int64 period_end_ms = 4;
|
int64 period_end_ms = 4;
|
||||||
int64 server_time_ms = 5;
|
int64 server_time_ms = 5;
|
||||||
|
repeated CPWeeklyRankEntry previous_entries = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetCPWeeklyRankConfigRequest {
|
message GetCPWeeklyRankConfigRequest {
|
||||||
@ -2464,6 +2526,14 @@ service MessageInboxService {
|
|||||||
rpc CreateFanoutJob(CreateFanoutJobRequest) returns (CreateFanoutJobResponse);
|
rpc CreateFanoutJob(CreateFanoutJobRequest) returns (CreateFanoutJobResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageActionConfirmService 拥有 IM 操作按钮的确认状态,业务执行仍回到 owner service。
|
||||||
|
service MessageActionConfirmService {
|
||||||
|
rpc AcceptActionConfirm(AcceptActionConfirmRequest) returns (AcceptActionConfirmResponse);
|
||||||
|
rpc RejectActionConfirm(RejectActionConfirmRequest) returns (RejectActionConfirmResponse);
|
||||||
|
rpc BatchGetActionConfirmStatus(BatchGetActionConfirmStatusRequest) returns (BatchGetActionConfirmStatusResponse);
|
||||||
|
rpc ListConversationActionConfirms(ListConversationActionConfirmsRequest) returns (ListConversationActionConfirmsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
// ActivityCronService 只给 cron-service 调用,活动和消息状态仍由 activity-service owner 修改。
|
// ActivityCronService 只给 cron-service 调用,活动和消息状态仍由 activity-service owner 修改。
|
||||||
service ActivityCronService {
|
service ActivityCronService {
|
||||||
rpc ProcessMessageFanoutBatch(CronBatchRequest) returns (CronBatchResponse);
|
rpc ProcessMessageFanoutBatch(CronBatchRequest) returns (CronBatchResponse);
|
||||||
|
|||||||
@ -496,6 +496,227 @@ var MessageInboxService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Metadata: "proto/activity/v1/activity.proto",
|
Metadata: "proto/activity/v1/activity.proto",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MessageActionConfirmService_AcceptActionConfirm_FullMethodName = "/hyapp.activity.v1.MessageActionConfirmService/AcceptActionConfirm"
|
||||||
|
MessageActionConfirmService_RejectActionConfirm_FullMethodName = "/hyapp.activity.v1.MessageActionConfirmService/RejectActionConfirm"
|
||||||
|
MessageActionConfirmService_BatchGetActionConfirmStatus_FullMethodName = "/hyapp.activity.v1.MessageActionConfirmService/BatchGetActionConfirmStatus"
|
||||||
|
MessageActionConfirmService_ListConversationActionConfirms_FullMethodName = "/hyapp.activity.v1.MessageActionConfirmService/ListConversationActionConfirms"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageActionConfirmServiceClient is the client API for MessageActionConfirmService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
//
|
||||||
|
// MessageActionConfirmService 拥有 IM 操作按钮的确认状态,业务执行仍回到 owner service。
|
||||||
|
type MessageActionConfirmServiceClient interface {
|
||||||
|
AcceptActionConfirm(ctx context.Context, in *AcceptActionConfirmRequest, opts ...grpc.CallOption) (*AcceptActionConfirmResponse, error)
|
||||||
|
RejectActionConfirm(ctx context.Context, in *RejectActionConfirmRequest, opts ...grpc.CallOption) (*RejectActionConfirmResponse, error)
|
||||||
|
BatchGetActionConfirmStatus(ctx context.Context, in *BatchGetActionConfirmStatusRequest, opts ...grpc.CallOption) (*BatchGetActionConfirmStatusResponse, error)
|
||||||
|
ListConversationActionConfirms(ctx context.Context, in *ListConversationActionConfirmsRequest, opts ...grpc.CallOption) (*ListConversationActionConfirmsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageActionConfirmServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessageActionConfirmServiceClient(cc grpc.ClientConnInterface) MessageActionConfirmServiceClient {
|
||||||
|
return &messageActionConfirmServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *messageActionConfirmServiceClient) AcceptActionConfirm(ctx context.Context, in *AcceptActionConfirmRequest, opts ...grpc.CallOption) (*AcceptActionConfirmResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(AcceptActionConfirmResponse)
|
||||||
|
err := c.cc.Invoke(ctx, MessageActionConfirmService_AcceptActionConfirm_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *messageActionConfirmServiceClient) RejectActionConfirm(ctx context.Context, in *RejectActionConfirmRequest, opts ...grpc.CallOption) (*RejectActionConfirmResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RejectActionConfirmResponse)
|
||||||
|
err := c.cc.Invoke(ctx, MessageActionConfirmService_RejectActionConfirm_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *messageActionConfirmServiceClient) BatchGetActionConfirmStatus(ctx context.Context, in *BatchGetActionConfirmStatusRequest, opts ...grpc.CallOption) (*BatchGetActionConfirmStatusResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(BatchGetActionConfirmStatusResponse)
|
||||||
|
err := c.cc.Invoke(ctx, MessageActionConfirmService_BatchGetActionConfirmStatus_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *messageActionConfirmServiceClient) ListConversationActionConfirms(ctx context.Context, in *ListConversationActionConfirmsRequest, opts ...grpc.CallOption) (*ListConversationActionConfirmsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListConversationActionConfirmsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, MessageActionConfirmService_ListConversationActionConfirms_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageActionConfirmServiceServer is the server API for MessageActionConfirmService service.
|
||||||
|
// All implementations must embed UnimplementedMessageActionConfirmServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
//
|
||||||
|
// MessageActionConfirmService 拥有 IM 操作按钮的确认状态,业务执行仍回到 owner service。
|
||||||
|
type MessageActionConfirmServiceServer interface {
|
||||||
|
AcceptActionConfirm(context.Context, *AcceptActionConfirmRequest) (*AcceptActionConfirmResponse, error)
|
||||||
|
RejectActionConfirm(context.Context, *RejectActionConfirmRequest) (*RejectActionConfirmResponse, error)
|
||||||
|
BatchGetActionConfirmStatus(context.Context, *BatchGetActionConfirmStatusRequest) (*BatchGetActionConfirmStatusResponse, error)
|
||||||
|
ListConversationActionConfirms(context.Context, *ListConversationActionConfirmsRequest) (*ListConversationActionConfirmsResponse, error)
|
||||||
|
mustEmbedUnimplementedMessageActionConfirmServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedMessageActionConfirmServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedMessageActionConfirmServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) AcceptActionConfirm(context.Context, *AcceptActionConfirmRequest) (*AcceptActionConfirmResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method AcceptActionConfirm not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) RejectActionConfirm(context.Context, *RejectActionConfirmRequest) (*RejectActionConfirmResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method RejectActionConfirm not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) BatchGetActionConfirmStatus(context.Context, *BatchGetActionConfirmStatusRequest) (*BatchGetActionConfirmStatusResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method BatchGetActionConfirmStatus not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) ListConversationActionConfirms(context.Context, *ListConversationActionConfirmsRequest) (*ListConversationActionConfirmsResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListConversationActionConfirms not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) mustEmbedUnimplementedMessageActionConfirmServiceServer() {
|
||||||
|
}
|
||||||
|
func (UnimplementedMessageActionConfirmServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeMessageActionConfirmServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to MessageActionConfirmServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeMessageActionConfirmServiceServer interface {
|
||||||
|
mustEmbedUnimplementedMessageActionConfirmServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterMessageActionConfirmServiceServer(s grpc.ServiceRegistrar, srv MessageActionConfirmServiceServer) {
|
||||||
|
// If the following call pancis, it indicates UnimplementedMessageActionConfirmServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&MessageActionConfirmService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _MessageActionConfirmService_AcceptActionConfirm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(AcceptActionConfirmRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).AcceptActionConfirm(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: MessageActionConfirmService_AcceptActionConfirm_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).AcceptActionConfirm(ctx, req.(*AcceptActionConfirmRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _MessageActionConfirmService_RejectActionConfirm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RejectActionConfirmRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).RejectActionConfirm(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: MessageActionConfirmService_RejectActionConfirm_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).RejectActionConfirm(ctx, req.(*RejectActionConfirmRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _MessageActionConfirmService_BatchGetActionConfirmStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(BatchGetActionConfirmStatusRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).BatchGetActionConfirmStatus(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: MessageActionConfirmService_BatchGetActionConfirmStatus_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).BatchGetActionConfirmStatus(ctx, req.(*BatchGetActionConfirmStatusRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _MessageActionConfirmService_ListConversationActionConfirms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListConversationActionConfirmsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).ListConversationActionConfirms(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: MessageActionConfirmService_ListConversationActionConfirms_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(MessageActionConfirmServiceServer).ListConversationActionConfirms(ctx, req.(*ListConversationActionConfirmsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageActionConfirmService_ServiceDesc is the grpc.ServiceDesc for MessageActionConfirmService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var MessageActionConfirmService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "hyapp.activity.v1.MessageActionConfirmService",
|
||||||
|
HandlerType: (*MessageActionConfirmServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "AcceptActionConfirm",
|
||||||
|
Handler: _MessageActionConfirmService_AcceptActionConfirm_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RejectActionConfirm",
|
||||||
|
Handler: _MessageActionConfirmService_RejectActionConfirm_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "BatchGetActionConfirmStatus",
|
||||||
|
Handler: _MessageActionConfirmService_BatchGetActionConfirmStatus_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListConversationActionConfirms",
|
||||||
|
Handler: _MessageActionConfirmService_ListConversationActionConfirms_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "proto/activity/v1/activity.proto",
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ActivityCronService_ProcessMessageFanoutBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessMessageFanoutBatch"
|
ActivityCronService_ProcessMessageFanoutBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessMessageFanoutBatch"
|
||||||
ActivityCronService_ProcessLevelRewardBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessLevelRewardBatch"
|
ActivityCronService_ProcessLevelRewardBatch_FullMethodName = "/hyapp.activity.v1.ActivityCronService/ProcessLevelRewardBatch"
|
||||||
|
|||||||
@ -472,6 +472,8 @@ type RoomUserJoined struct {
|
|||||||
EntryVehicle *RoomEntryVehicleSnapshot `protobuf:"bytes,4,opt,name=entry_vehicle,json=entryVehicle,proto3" json:"entry_vehicle,omitempty"`
|
EntryVehicle *RoomEntryVehicleSnapshot `protobuf:"bytes,4,opt,name=entry_vehicle,json=entryVehicle,proto3" json:"entry_vehicle,omitempty"`
|
||||||
CountryId int64 `protobuf:"varint,5,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"`
|
CountryId int64 `protobuf:"varint,5,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"`
|
||||||
RegionId int64 `protobuf:"varint,6,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"`
|
RegionId int64 `protobuf:"varint,6,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"`
|
||||||
|
// is_robot 标记本次进房来自房间机器人调度,统计服务必须跳过活跃用户口径。
|
||||||
|
IsRobot bool `protobuf:"varint,7,opt,name=is_robot,json=isRobot,proto3" json:"is_robot,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomUserJoined) Reset() {
|
func (x *RoomUserJoined) Reset() {
|
||||||
@ -546,6 +548,13 @@ func (x *RoomUserJoined) GetRegionId() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RoomUserJoined) GetIsRobot() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsRobot
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// RoomUserLeft 表达用户离房成功。
|
// RoomUserLeft 表达用户离房成功。
|
||||||
type RoomUserLeft struct {
|
type RoomUserLeft struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@ -1233,6 +1242,16 @@ type RoomGiftSent struct {
|
|||||||
SyntheticLuckyGift bool `protobuf:"varint,21,opt,name=synthetic_lucky_gift,json=syntheticLuckyGift,proto3" json:"synthetic_lucky_gift,omitempty"`
|
SyntheticLuckyGift bool `protobuf:"varint,21,opt,name=synthetic_lucky_gift,json=syntheticLuckyGift,proto3" json:"synthetic_lucky_gift,omitempty"`
|
||||||
// real_room_robot_gift 标记真人房机器人送出的真实热度礼物,用于 Databi 单独统计机器人投放价值。
|
// real_room_robot_gift 标记真人房机器人送出的真实热度礼物,用于 Databi 单独统计机器人投放价值。
|
||||||
RealRoomRobotGift bool `protobuf:"varint,22,opt,name=real_room_robot_gift,json=realRoomRobotGift,proto3" json:"real_room_robot_gift,omitempty"`
|
RealRoomRobotGift bool `protobuf:"varint,22,opt,name=real_room_robot_gift,json=realRoomRobotGift,proto3" json:"real_room_robot_gift,omitempty"`
|
||||||
|
// sender_name 是送礼入口固化的展示昵称,只用于客户端 IM 展示兜底,不作为用户主资料事实。
|
||||||
|
SenderName string `protobuf:"bytes,23,opt,name=sender_name,json=senderName,proto3" json:"sender_name,omitempty"`
|
||||||
|
SenderAvatar string `protobuf:"bytes,24,opt,name=sender_avatar,json=senderAvatar,proto3" json:"sender_avatar,omitempty"`
|
||||||
|
SenderDisplayUserId string `protobuf:"bytes,25,opt,name=sender_display_user_id,json=senderDisplayUserId,proto3" json:"sender_display_user_id,omitempty"`
|
||||||
|
SenderPrettyDisplayUserId string `protobuf:"bytes,26,opt,name=sender_pretty_display_user_id,json=senderPrettyDisplayUserId,proto3" json:"sender_pretty_display_user_id,omitempty"`
|
||||||
|
// receiver_nickname 是收礼目标在送礼瞬间的展示昵称;字段名匹配 Flutter 已有解析逻辑。
|
||||||
|
ReceiverNickname string `protobuf:"bytes,27,opt,name=receiver_nickname,json=receiverNickname,proto3" json:"receiver_nickname,omitempty"`
|
||||||
|
ReceiverAvatar string `protobuf:"bytes,28,opt,name=receiver_avatar,json=receiverAvatar,proto3" json:"receiver_avatar,omitempty"`
|
||||||
|
ReceiverDisplayUserId string `protobuf:"bytes,29,opt,name=receiver_display_user_id,json=receiverDisplayUserId,proto3" json:"receiver_display_user_id,omitempty"`
|
||||||
|
ReceiverPrettyDisplayUserId string `protobuf:"bytes,30,opt,name=receiver_pretty_display_user_id,json=receiverPrettyDisplayUserId,proto3" json:"receiver_pretty_display_user_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RoomGiftSent) Reset() {
|
func (x *RoomGiftSent) Reset() {
|
||||||
@ -1419,6 +1438,62 @@ func (x *RoomGiftSent) GetRealRoomRobotGift() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetSenderName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SenderName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetSenderAvatar() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SenderAvatar
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetSenderDisplayUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SenderDisplayUserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetSenderPrettyDisplayUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SenderPrettyDisplayUserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetReceiverNickname() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReceiverNickname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetReceiverAvatar() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReceiverAvatar
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetReceiverDisplayUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReceiverDisplayUserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RoomGiftSent) GetReceiverPrettyDisplayUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReceiverPrettyDisplayUserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。
|
// RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。
|
||||||
// 它复用客户端 lucky_gift_drawn 表现协议,但不代表真实奖池抽奖和钱包返奖事实。
|
// 它复用客户端 lucky_gift_drawn 表现协议,但不代表真实奖池抽奖和钱包返奖事实。
|
||||||
type RoomRobotLuckyGiftDrawn struct {
|
type RoomRobotLuckyGiftDrawn struct {
|
||||||
@ -2353,7 +2428,7 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{
|
|||||||
0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74,
|
0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74,
|
||||||
0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72,
|
0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72,
|
||||||
0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55,
|
0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x95, 0x02, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55,
|
||||||
0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
|
0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
|
||||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
|
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
@ -2369,309 +2444,335 @@ var file_proto_events_room_v1_events_proto_rawDesc = []byte{
|
|||||||
0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75,
|
0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75,
|
||||||
0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e,
|
0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f,
|
0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f,
|
||||||
0x6e, 0x49, 0x64, 0x22, 0x27, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4c,
|
0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18,
|
||||||
0x65, 0x66, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x22, 0x27,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x0a,
|
0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x17,
|
||||||
0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63,
|
0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x43,
|
||||||
0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16,
|
0x6c, 0x6f, 0x73, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75,
|
||||||
0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63,
|
||||||
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xd8, 0x03, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d,
|
0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61,
|
||||||
0x69, 0x63, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74,
|
0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
|
||||||
0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
0x6e, 0x22, 0xd8, 0x03, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x43, 0x68, 0x61,
|
||||||
0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a,
|
0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73,
|
||||||
0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
|
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74,
|
||||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65,
|
0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67,
|
||||||
0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74,
|
0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74,
|
0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b,
|
||||||
0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x0a, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x05, 0x52, 0x06, 0x74, 0x6f, 0x53, 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74,
|
0x05, 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74,
|
||||||
0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
0x6f, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x6f,
|
||||||
0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
0x53, 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e,
|
||||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69,
|
0x6d, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06,
|
||||||
0x73, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06,
|
0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x73, 0x74,
|
||||||
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65,
|
0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69,
|
||||||
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f,
|
0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
|
||||||
0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28,
|
0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
|
||||||
0x03, 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69,
|
0x2e, 0x0a, 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x64, 0x65, 0x61, 0x64, 0x6c,
|
||||||
0x6e, 0x65, 0x4d, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f,
|
0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x70, 0x75,
|
||||||
0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20,
|
0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x73, 0x12,
|
||||||
0x01, 0x28, 0x03, 0x52, 0x12, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e,
|
0x31, 0x0a, 0x15, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||||
0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x6d,
|
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12,
|
||||||
0x75, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x4d,
|
0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65,
|
||||||
0x75, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x73, 0x74, 0x61,
|
0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18,
|
||||||
0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x53,
|
0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
|
0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c,
|
||||||
0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75,
|
0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f,
|
||||||
0x65, 0x22, 0x68, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74,
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72,
|
||||||
0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x68, 0x0a, 0x11,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
|
0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x4c, 0x6f, 0x63, 0x6b, 0x65,
|
||||||
0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65,
|
0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||||
0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61,
|
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55,
|
||||||
0x74, 0x4e, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20,
|
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f,
|
||||||
0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x52,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x16,
|
||||||
0x6f, 0x6f, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x68,
|
0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06,
|
||||||
|
0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x56, 0x0a, 0x16, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x68,
|
||||||
|
0x61, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
||||||
|
0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||||
|
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73,
|
||||||
|
0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x7d,
|
||||||
|
0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x68,
|
||||||
0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75,
|
0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75,
|
||||||
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63,
|
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63,
|
||||||
0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61,
|
0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63,
|
||||||
0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62,
|
0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65,
|
||||||
0x6c, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x13, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77,
|
0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67,
|
||||||
0x6f, 0x72, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63,
|
0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69,
|
||||||
0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x76, 0x0a,
|
||||||
0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16,
|
0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||||
0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06,
|
0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||||
0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c,
|
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55,
|
||||||
0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
|
||||||
0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e,
|
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74,
|
||||||
0x49, 0x64, 0x22, 0x76, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x43,
|
0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65,
|
||||||
0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e,
|
||||||
|
0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6f, 0x0a, 0x0d, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65,
|
||||||
|
0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
|
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
|
||||||
0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61,
|
0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61,
|
||||||
0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64,
|
0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64,
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x6f, 0x0a, 0x0d, 0x52, 0x6f,
|
0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73,
|
||||||
0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61,
|
0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f,
|
||||||
0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
|
0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e,
|
||||||
0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55,
|
|
||||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03,
|
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, 0x52,
|
|
||||||
0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, 0x0a,
|
|
||||||
0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49,
|
|
||||||
0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65,
|
|
||||||
0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x55,
|
|
||||||
0x73, 0x65, 0x72, 0x55, 0x6e, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61,
|
|
||||||
0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
|
||||||
0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
|
|
||||||
0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55,
|
|
||||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xbc, 0x06, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69,
|
|
||||||
0x66, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72,
|
|
||||||
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
|
|
||||||
0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e,
|
|
||||||
0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72,
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72,
|
||||||
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
0x49, 0x64, 0x22, 0x5c, 0x0a, 0x10, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x55, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67,
|
0x62, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f,
|
||||||
0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52,
|
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61,
|
||||||
0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69,
|
0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61,
|
||||||
0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
|
0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c,
|
0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64,
|
||||||
0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18,
|
0x22, 0xce, 0x09, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e,
|
||||||
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65,
|
0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62,
|
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65,
|
||||||
0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01,
|
0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65,
|
||||||
0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f,
|
0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69,
|
0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a,
|
||||||
0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63,
|
|
||||||
0x6f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52,
|
|
||||||
0x09, 0x63, 0x6f, 0x69, 0x6e, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f,
|
|
||||||
0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09,
|
|
||||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67,
|
|
||||||
0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65,
|
|
||||||
0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74,
|
|
||||||
0x79, 0x70, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
|
||||||
0x67, 0x69, 0x66, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10,
|
|
||||||
0x63, 0x70, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65,
|
|
||||||
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e,
|
|
||||||
0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e,
|
|
||||||
0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e,
|
|
||||||
0x5f, 0x75, 0x72, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74,
|
|
||||||
0x49, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f,
|
|
||||||
0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
|
|
||||||
0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75,
|
|
||||||
0x65, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74,
|
|
||||||
0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x69,
|
|
||||||
0x66, 0x74, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x22, 0x0a,
|
|
||||||
0x0d, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x14,
|
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66,
|
|
||||||
0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x5f, 0x6c,
|
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52,
|
|
||||||
0x12, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47,
|
|
||||||
0x69, 0x66, 0x74, 0x12, 0x2f, 0x0a, 0x14, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x6f, 0x6d,
|
|
||||||
0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28,
|
|
||||||
0x08, 0x52, 0x11, 0x72, 0x65, 0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74,
|
|
||||||
0x47, 0x69, 0x66, 0x74, 0x22, 0xf7, 0x04, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62,
|
|
||||||
0x6f, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e,
|
|
||||||
0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d,
|
|
||||||
0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
|
|
||||||
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64,
|
|
||||||
0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24,
|
|
||||||
0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,
|
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73,
|
|
||||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18,
|
|
||||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a,
|
|
||||||
0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
|
|
||||||
0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07,
|
|
||||||
0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
|
|
||||||
0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c,
|
|
||||||
0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 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, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77,
|
|
||||||
0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x6f, 0x6f, 0x6d,
|
|
||||||
0x5f, 0x61, 0x74, 0x6d, 0x6f, 0x73, 0x70, 0x68, 0x65, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61,
|
|
||||||
0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19,
|
|
||||||
0x72, 0x6f, 0x6f, 0x6d, 0x41, 0x74, 0x6d, 0x6f, 0x73, 0x70, 0x68, 0x65, 0x72, 0x65, 0x52, 0x65,
|
|
||||||
0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x63, 0x74,
|
|
||||||
0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x69, 0x64, 0x79, 0x5f, 0x63, 0x6f,
|
|
||||||
0x69, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x61, 0x63, 0x74, 0x69, 0x76,
|
|
||||||
0x69, 0x74, 0x79, 0x53, 0x75, 0x62, 0x73, 0x69, 0x64, 0x79, 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, 0x0c, 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, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
|
||||||
0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72,
|
|
||||||
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73,
|
|
||||||
0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e,
|
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67,
|
|
||||||
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f,
|
|
||||||
0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74,
|
|
||||||
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x18, 0x10, 0x20,
|
|
||||||
0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x22, 0x4a,
|
|
||||||
0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
|
||||||
0x64, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65,
|
|
||||||
0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63,
|
|
||||||
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f,
|
|
||||||
0x6f, 0x6d, 0x52, 0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a,
|
|
||||||
0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
|
||||||
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18,
|
|
||||||
0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
|
|
||||||
0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x15,
|
|
||||||
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64,
|
|
||||||
0x47, 0x72, 0x61, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f,
|
|
||||||
0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61,
|
|
||||||
0x72, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
|
|
||||||
0x24, 0x0a, 0x0e, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49,
|
|
||||||
0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
|
||||||
0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49,
|
|
||||||
0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d,
|
|
||||||
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
|
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c,
|
|
||||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12,
|
|
||||||
0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74,
|
|
||||||
0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
|
|
||||||
0x75, 0x73, 0x22, 0xeb, 0x03, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65,
|
|
||||||
0x74, 0x46, 0x75, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09,
|
|
||||||
0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
|
||||||
0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76,
|
|
||||||
0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12,
|
|
||||||
0x1d, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20,
|
|
||||||
0x01, 0x28, 0x03, 0x52, 0x09, 0x61, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x30,
|
|
||||||
0x0a, 0x14, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x65,
|
|
||||||
0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x65, 0x66,
|
|
||||||
0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c,
|
|
||||||
0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x66, 0x75, 0x65,
|
|
||||||
0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f,
|
|
||||||
0x77, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
|
|
||||||
0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72,
|
|
||||||
0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c,
|
|
||||||
0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03,
|
|
||||||
0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12,
|
|
||||||
0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
|
|
||||||
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74,
|
|
||||||
0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65,
|
|
||||||
0x73, 0x65, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65,
|
|
||||||
0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52,
|
|
||||||
0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a,
|
|
||||||
0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
|
||||||
0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63,
|
0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63,
|
||||||
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74,
|
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74,
|
||||||
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56,
|
||||||
0x6e, 0x64, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f,
|
0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f,
|
||||||
0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52,
|
0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64,
|
0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74,
|
||||||
0x22, 0x82, 0x04, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49,
|
0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65,
|
||||||
0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74,
|
0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65,
|
0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d,
|
||||||
0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01,
|
0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01,
|
||||||
0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72,
|
0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a,
|
||||||
0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
|
0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e,
|
0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x73,
|
||||||
0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04,
|
0x70, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x69, 0x6e,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68,
|
0x53, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79,
|
||||||
0x6f, 0x6c, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61,
|
0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||||
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x69,
|
0x72, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
|
||||||
0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63,
|
0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49,
|
||||||
0x68, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c,
|
0x64, 0x12, 0x24, 0x0a, 0x0e, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x63,
|
||||||
0x61, 0x75, 0x6e, 0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x72, 0x6f,
|
0x6f, 0x64, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x69, 0x66, 0x74, 0x54,
|
||||||
0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01,
|
0x79, 0x70, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x5f, 0x72, 0x65,
|
||||||
0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f,
|
0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
||||||
0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65,
|
0x09, 0x52, 0x0e, 0x63, 0x70, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70,
|
||||||
0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76,
|
0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f,
|
||||||
0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x20,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22,
|
||||||
0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09,
|
0x0a, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64,
|
0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x69, 0x66, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x55,
|
||||||
0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72,
|
0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x61, 0x6e, 0x69, 0x6d, 0x61,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74,
|
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,
|
||||||
0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d,
|
0x67, 0x69, 0x66, 0x74, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c,
|
||||||
0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f,
|
0x12, 0x2a, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f,
|
||||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f,
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x61, 0x72,
|
||||||
0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
0x67, 0x65, 0x74, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11,
|
||||||
0x72, 0x6f, 0x6f, 0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72,
|
0x67, 0x69, 0x66, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65,
|
||||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18,
|
0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x45, 0x66, 0x66,
|
||||||
0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x76,
|
0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x69, 0x73, 0x5f, 0x72,
|
||||||
0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61,
|
0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65,
|
0x0b, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x30, 0x0a, 0x14,
|
||||||
0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0xe6, 0x02, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f,
|
0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x5f, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x5f,
|
||||||
0x63, 0x6b, 0x65, 0x74, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09,
|
0x67, 0x69, 0x66, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x73, 0x79, 0x6e, 0x74,
|
||||||
0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x68, 0x65, 0x74, 0x69, 0x63, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x2f,
|
||||||
0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76,
|
0x0a, 0x14, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x72, 0x6f, 0x62, 0x6f,
|
||||||
0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12,
|
0x74, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x18, 0x16, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65,
|
||||||
0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20,
|
0x61, 0x6c, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12,
|
||||||
0x01, 0x28, 0x05, 0x52, 0x09, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24,
|
0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x17,
|
||||||
0x0a, 0x0e, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64,
|
0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61,
|
||||||
0x41, 0x74, 0x4d, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74,
|
0x72, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x41,
|
||||||
0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74,
|
0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f,
|
||||||
0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65,
|
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18,
|
||||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31,
|
0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x44, 0x69, 0x73,
|
||||||
0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65,
|
0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x65,
|
||||||
0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
|
0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70,
|
||||||
0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27,
|
0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1a, 0x20, 0x01, 0x28,
|
||||||
0x0a, 0x10, 0x69, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
0x09, 0x52, 0x19, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44,
|
||||||
0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d,
|
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11,
|
||||||
0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72,
|
0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d,
|
||||||
0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
|
||||||
0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e,
|
0x72, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63,
|
||||||
|
0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x1c, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74,
|
||||||
|
0x61, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64,
|
||||||
|
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1d,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x69,
|
||||||
|
0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x72,
|
||||||
|
0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64,
|
||||||
|
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x50, 0x72,
|
||||||
|
0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||||
|
0x64, 0x22, 0xf7, 0x04, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x4c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x6e, 0x12, 0x17, 0x0a,
|
||||||
|
0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
|
0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
||||||
|
0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d,
|
||||||
|
0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f,
|
||||||
|
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73,
|
||||||
|
0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74,
|
||||||
|
0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,
|
||||||
|
0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49,
|
||||||
|
0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69,
|
||||||
|
0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,
|
||||||
|
0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f,
|
||||||
|
0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c,
|
||||||
|
0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72,
|
||||||
|
0x5f, 0x70, 0x70, 0x6d, 0x18, 0x08, 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, 0x09,
|
||||||
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64,
|
||||||
|
0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x61, 0x74,
|
||||||
|
0x6d, 0x6f, 0x73, 0x70, 0x68, 0x65, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f,
|
||||||
|
0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x72, 0x6f, 0x6f,
|
||||||
|
0x6d, 0x41, 0x74, 0x6d, 0x6f, 0x73, 0x70, 0x68, 0x65, 0x72, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72,
|
||||||
|
0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69,
|
||||||
|
0x74, 0x79, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x69, 0x64, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73,
|
||||||
|
0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
|
||||||
|
0x53, 0x75, 0x62, 0x73, 0x69, 0x64, 0x79, 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, 0x0c, 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, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
|
||||||
|
0x5f, 0x6d, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||||
|
0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c,
|
||||||
|
0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
||||||
|
0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e,
|
||||||
|
0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18, 0x0f,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x1c, 0x0a,
|
||||||
|
0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08,
|
||||||
|
0x52, 0x09, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x22, 0x4a, 0x0a, 0x0f, 0x52,
|
||||||
|
0x6f, 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x14,
|
||||||
|
0x0a, 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64,
|
||||||
|
0x65, 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f,
|
||||||
|
0x68, 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72,
|
||||||
|
0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52,
|
||||||
|
0x61, 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
|
||||||
|
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
|
||||||
|
0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66,
|
||||||
|
0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67,
|
||||||
|
0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x15, 0x52, 0x6f, 0x6f,
|
||||||
|
0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61,
|
||||||
|
0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x6c,
|
||||||
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x52,
|
||||||
|
0x6f, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
||||||
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e,
|
||||||
|
0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d,
|
||||||
|
0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67,
|
||||||
|
0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72,
|
||||||
|
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x21,
|
||||||
|
0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x63, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x63, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08,
|
||||||
|
0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
|
0x67, 0x72, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||||
|
0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
|
||||||
|
0xeb, 0x03, 0x0a, 0x15, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x46, 0x75,
|
||||||
|
0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63,
|
||||||
|
0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f,
|
||||||
|
0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a,
|
||||||
|
0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
|
||||||
|
0x52, 0x09, 0x61, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x30, 0x0a, 0x14, 0x65,
|
||||||
|
0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x66,
|
||||||
|
0x75, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x65, 0x66, 0x66, 0x65, 0x63,
|
||||||
|
0x74, 0x69, 0x76, 0x65, 0x41, 0x64, 0x64, 0x65, 0x64, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x23, 0x0a,
|
||||||
|
0x0d, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x05,
|
||||||
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x46, 0x75,
|
||||||
|
0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x75,
|
||||||
|
0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||||
|
0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65, 0x6c, 0x5f, 0x74, 0x68,
|
||||||
|
0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66,
|
||||||
|
0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x16, 0x0a, 0x06,
|
||||||
|
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
|
||||||
|
0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74,
|
||||||
|
0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74,
|
||||||
|
0x41, 0x74, 0x4d, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75,
|
||||||
|
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65,
|
||||||
|
0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69,
|
||||||
|
0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66,
|
||||||
|
0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
|
||||||
|
0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75,
|
||||||
|
0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64,
|
||||||
|
0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49,
|
||||||
|
0x64, 0x12, 0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67,
|
||||||
|
0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69,
|
||||||
|
0x73, 0x69, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x82, 0x04,
|
||||||
|
0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x67, 0x6e, 0x69,
|
||||||
|
0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64,
|
||||||
|
0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||||
|
0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
|
||||||
|
0x74, 0x5f, 0x66, 0x75, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75,
|
||||||
|
0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x75, 0x65, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x75, 0x65,
|
||||||
|
0x6c, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
|
0x03, 0x52, 0x0d, 0x66, 0x75, 0x65, 0x6c, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64,
|
||||||
|
0x12, 0x22, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d,
|
||||||
|
0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x64,
|
||||||
|
0x41, 0x74, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x61,
|
||||||
|
0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x75, 0x6e,
|
||||||
|
0x63, 0x68, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63,
|
||||||
|
0x61, 0x73, 0x74, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x0e, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12,
|
||||||
|
0x2a, 0x0a, 0x11, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f,
|
||||||
|
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x76, 0x69, 0x73, 0x69,
|
||||||
|
0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x74,
|
||||||
|
0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28,
|
||||||
|
0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a,
|
||||||
|
0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,
|
||||||
|
0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x55,
|
||||||
|
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||||
|
0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x73, 0x68, 0x6f,
|
||||||
|
0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x6f, 0x6f,
|
||||||
|
0x6d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x6f, 0x63, 0x6b,
|
||||||
|
0x65, 0x74, 0x5f, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x76, 0x65, 0x72, 0x55,
|
||||||
|
0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d,
|
||||||
|
0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74,
|
||||||
|
0x4d, 0x73, 0x22, 0xe6, 0x02, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65,
|
||||||
|
0x74, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63,
|
||||||
|
0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f,
|
||||||
|
0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a,
|
||||||
|
0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
|
||||||
|
0x52, 0x09, 0x6e, 0x65, 0x78, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x6c,
|
||||||
|
0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20,
|
||||||
|
0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x64, 0x41, 0x74, 0x4d,
|
||||||
|
0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73,
|
||||||
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x72, 0x65, 0x73, 0x65, 0x74, 0x41, 0x74, 0x4d,
|
||||||
|
0x73, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x6f, 0x70, 0x31, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
|
||||||
|
0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x70, 0x31, 0x55, 0x73, 0x65,
|
||||||
|
0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x69, 0x74, 0x65, 0x72, 0x5f, 0x75,
|
||||||
|
0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x67,
|
||||||
|
0x6e, 0x69, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x10, 0x69,
|
||||||
|
0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18,
|
||||||
|
0x08, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65,
|
||||||
|
0x72, 0x49, 0x64, 0x73, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18,
|
||||||
|
0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76,
|
||||||
|
0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f,
|
||||||
|
0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61,
|
||||||
|
0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x17,
|
||||||
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64,
|
0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64,
|
||||||
0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x93,
|
0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x63, 0x6b, 0x65,
|
||||||
0x01, 0x0a, 0x17, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77,
|
0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x63, 0x6b,
|
||||||
0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f,
|
0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20,
|
||||||
0x63, 0x6b, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72,
|
0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x65,
|
||||||
0x6f, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c,
|
0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x45, 0x0a,
|
0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e,
|
||||||
0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b,
|
0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x77,
|
||||||
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f,
|
0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64,
|
||||||
0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74,
|
0x73, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
|
||||||
0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x77,
|
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74,
|
||||||
0x61, 0x72, 0x64, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f,
|
0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x65, 0x76,
|
||||||
0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76,
|
0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f,
|
|
||||||
0x6d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -65,6 +65,8 @@ message RoomUserJoined {
|
|||||||
RoomEntryVehicleSnapshot entry_vehicle = 4;
|
RoomEntryVehicleSnapshot entry_vehicle = 4;
|
||||||
int64 country_id = 5;
|
int64 country_id = 5;
|
||||||
int64 region_id = 6;
|
int64 region_id = 6;
|
||||||
|
// is_robot 标记本次进房来自房间机器人调度,统计服务必须跳过活跃用户口径。
|
||||||
|
bool is_robot = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomUserLeft 表达用户离房成功。
|
// RoomUserLeft 表达用户离房成功。
|
||||||
@ -172,6 +174,16 @@ message RoomGiftSent {
|
|||||||
bool synthetic_lucky_gift = 21;
|
bool synthetic_lucky_gift = 21;
|
||||||
// real_room_robot_gift 标记真人房机器人送出的真实热度礼物,用于 Databi 单独统计机器人投放价值。
|
// real_room_robot_gift 标记真人房机器人送出的真实热度礼物,用于 Databi 单独统计机器人投放价值。
|
||||||
bool real_room_robot_gift = 22;
|
bool real_room_robot_gift = 22;
|
||||||
|
// sender_name 是送礼入口固化的展示昵称,只用于客户端 IM 展示兜底,不作为用户主资料事实。
|
||||||
|
string sender_name = 23;
|
||||||
|
string sender_avatar = 24;
|
||||||
|
string sender_display_user_id = 25;
|
||||||
|
string sender_pretty_display_user_id = 26;
|
||||||
|
// receiver_nickname 是收礼目标在送礼瞬间的展示昵称;字段名匹配 Flutter 已有解析逻辑。
|
||||||
|
string receiver_nickname = 27;
|
||||||
|
string receiver_avatar = 28;
|
||||||
|
string receiver_display_user_id = 29;
|
||||||
|
string receiver_pretty_display_user_id = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。
|
// RoomRobotLuckyGiftDrawn 是机器人房间专用幸运礼物展示事件。
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -643,6 +643,8 @@ message JoinRoomRequest {
|
|||||||
RoomEntryVehicleSnapshot entry_vehicle = 4;
|
RoomEntryVehicleSnapshot entry_vehicle = 4;
|
||||||
int64 actor_country_id = 5;
|
int64 actor_country_id = 5;
|
||||||
int64 actor_region_id = 6;
|
int64 actor_region_id = 6;
|
||||||
|
// actor_is_robot 只由 room-service 内部机器人调度链路设置,外部 gateway 进房保持 false。
|
||||||
|
bool actor_is_robot = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinRoomResponse 返回加入后的房间快照。
|
// JoinRoomResponse 返回加入后的房间快照。
|
||||||
@ -1010,6 +1012,16 @@ message SendGiftTargetHostScope {
|
|||||||
int64 target_agency_owner_user_id = 4;
|
int64 target_agency_owner_user_id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendGiftDisplayProfile 是 gateway 在送礼入口固化的轻量展示快照。
|
||||||
|
// room-service 只把它写入事件和 IM,不把用户资料扩散到 Room Cell 核心状态。
|
||||||
|
message SendGiftDisplayProfile {
|
||||||
|
int64 user_id = 1;
|
||||||
|
string username = 2;
|
||||||
|
string avatar = 3;
|
||||||
|
string display_user_id = 4;
|
||||||
|
string pretty_display_user_id = 5;
|
||||||
|
}
|
||||||
|
|
||||||
// SendGiftRequest 是首版房间内最重要的变现命令。
|
// SendGiftRequest 是首版房间内最重要的变现命令。
|
||||||
message SendGiftRequest {
|
message SendGiftRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
@ -1037,6 +1049,10 @@ message SendGiftRequest {
|
|||||||
string source = 14;
|
string source = 14;
|
||||||
// target_host_scopes 覆盖批量 target 的工资入账快照;单目标旧字段仍保留兼容老调用链。
|
// target_host_scopes 覆盖批量 target 的工资入账快照;单目标旧字段仍保留兼容老调用链。
|
||||||
repeated SendGiftTargetHostScope target_host_scopes = 15;
|
repeated SendGiftTargetHostScope target_host_scopes = 15;
|
||||||
|
// sender_display_profile 是 gateway 从 user-service 读取的送礼人展示快照,用于 IM 兜底展示。
|
||||||
|
SendGiftDisplayProfile sender_display_profile = 16;
|
||||||
|
// target_display_profiles 是每个收礼目标的展示快照;多目标 IM 按 target_user_id 取对应项。
|
||||||
|
repeated SendGiftDisplayProfile target_display_profiles = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendGiftResponse 返回扣费成功并落到房间后的状态结果。
|
// SendGiftResponse 返回扣费成功并落到房间后的状态结果。
|
||||||
|
|||||||
@ -1142,6 +1142,274 @@ func (x *CheckLoginRiskIPWhitelistResponse) GetWhitelisted() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterRiskConfig 是注册链路读取的风控配置。
|
||||||
|
type RegisterRiskConfig struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
|
||||||
|
MaxAccountsPerDevice int32 `protobuf:"varint,2,opt,name=max_accounts_per_device,json=maxAccountsPerDevice,proto3" json:"max_accounts_per_device,omitempty"`
|
||||||
|
UpdatedAtMs int64 `protobuf:"varint,3,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
|
||||||
|
UpdatedByAdminId int64 `protobuf:"varint,4,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) Reset() {
|
||||||
|
*x = RegisterRiskConfig{}
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[15]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RegisterRiskConfig) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[15]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RegisterRiskConfig.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RegisterRiskConfig) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{15}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) GetAppCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AppCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) GetMaxAccountsPerDevice() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MaxAccountsPerDevice
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) GetUpdatedAtMs() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UpdatedAtMs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterRiskConfig) GetUpdatedByAdminId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UpdatedByAdminId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegisterRiskConfigRequest 查询当前 App 的注册风控配置。
|
||||||
|
type GetRegisterRiskConfigRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigRequest) Reset() {
|
||||||
|
*x = GetRegisterRiskConfigRequest{}
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[16]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetRegisterRiskConfigRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[16]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetRegisterRiskConfigRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetRegisterRiskConfigRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{16}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigRequest) GetMeta() *RequestMeta {
|
||||||
|
if x != nil {
|
||||||
|
return x.Meta
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRegisterRiskConfigResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Config *RegisterRiskConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigResponse) Reset() {
|
||||||
|
*x = GetRegisterRiskConfigResponse{}
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[17]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetRegisterRiskConfigResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[17]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetRegisterRiskConfigResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetRegisterRiskConfigResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{17}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetRegisterRiskConfigResponse) GetConfig() *RegisterRiskConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.Config
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRegisterRiskConfigRequest 由后台保存当前 App 的注册风控配置。
|
||||||
|
type UpdateRegisterRiskConfigRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
|
||||||
|
MaxAccountsPerDevice int32 `protobuf:"varint,2,opt,name=max_accounts_per_device,json=maxAccountsPerDevice,proto3" json:"max_accounts_per_device,omitempty"`
|
||||||
|
OperatorAdminId int64 `protobuf:"varint,3,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) Reset() {
|
||||||
|
*x = UpdateRegisterRiskConfigRequest{}
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[18]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UpdateRegisterRiskConfigRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[18]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use UpdateRegisterRiskConfigRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*UpdateRegisterRiskConfigRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{18}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) GetMeta() *RequestMeta {
|
||||||
|
if x != nil {
|
||||||
|
return x.Meta
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) GetMaxAccountsPerDevice() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MaxAccountsPerDevice
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigRequest) GetOperatorAdminId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.OperatorAdminId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRegisterRiskConfigResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Config *RegisterRiskConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigResponse) Reset() {
|
||||||
|
*x = UpdateRegisterRiskConfigResponse{}
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[19]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UpdateRegisterRiskConfigResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_user_v1_auth_proto_msgTypes[19]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use UpdateRegisterRiskConfigResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*UpdateRegisterRiskConfigResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{19}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *UpdateRegisterRiskConfigResponse) GetConfig() *RegisterRiskConfig {
|
||||||
|
if x != nil {
|
||||||
|
return x.Config
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
|
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
|
||||||
type AppHeartbeatRequest struct {
|
type AppHeartbeatRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@ -1155,7 +1423,7 @@ type AppHeartbeatRequest struct {
|
|||||||
|
|
||||||
func (x *AppHeartbeatRequest) Reset() {
|
func (x *AppHeartbeatRequest) Reset() {
|
||||||
*x = AppHeartbeatRequest{}
|
*x = AppHeartbeatRequest{}
|
||||||
mi := &file_proto_user_v1_auth_proto_msgTypes[15]
|
mi := &file_proto_user_v1_auth_proto_msgTypes[20]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1167,7 +1435,7 @@ func (x *AppHeartbeatRequest) String() string {
|
|||||||
func (*AppHeartbeatRequest) ProtoMessage() {}
|
func (*AppHeartbeatRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message {
|
func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_user_v1_auth_proto_msgTypes[15]
|
mi := &file_proto_user_v1_auth_proto_msgTypes[20]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1180,7 +1448,7 @@ func (x *AppHeartbeatRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use AppHeartbeatRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use AppHeartbeatRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*AppHeartbeatRequest) Descriptor() ([]byte, []int) {
|
func (*AppHeartbeatRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{15}
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{20}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AppHeartbeatRequest) GetMeta() *RequestMeta {
|
func (x *AppHeartbeatRequest) GetMeta() *RequestMeta {
|
||||||
@ -1218,7 +1486,7 @@ type AppHeartbeatResponse struct {
|
|||||||
|
|
||||||
func (x *AppHeartbeatResponse) Reset() {
|
func (x *AppHeartbeatResponse) Reset() {
|
||||||
*x = AppHeartbeatResponse{}
|
*x = AppHeartbeatResponse{}
|
||||||
mi := &file_proto_user_v1_auth_proto_msgTypes[16]
|
mi := &file_proto_user_v1_auth_proto_msgTypes[21]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1230,7 +1498,7 @@ func (x *AppHeartbeatResponse) String() string {
|
|||||||
func (*AppHeartbeatResponse) ProtoMessage() {}
|
func (*AppHeartbeatResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message {
|
func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proto_user_v1_auth_proto_msgTypes[16]
|
mi := &file_proto_user_v1_auth_proto_msgTypes[21]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1243,7 +1511,7 @@ func (x *AppHeartbeatResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use AppHeartbeatResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use AppHeartbeatResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*AppHeartbeatResponse) Descriptor() ([]byte, []int) {
|
func (*AppHeartbeatResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{16}
|
return file_proto_user_v1_auth_proto_rawDescGZIP(), []int{21}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AppHeartbeatResponse) GetAccepted() bool {
|
func (x *AppHeartbeatResponse) GetAccepted() bool {
|
||||||
@ -1447,83 +1715,138 @@ var file_proto_user_v1_auth_proto_rawDesc = []byte{
|
|||||||
0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74,
|
0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74,
|
||||||
0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c,
|
0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x68, 0x69, 0x74, 0x65, 0x6c,
|
||||||
0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0x7d, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
|
0x69, 0x73, 0x74, 0x65, 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||||
0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04,
|
0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08,
|
||||||
0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61,
|
0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61,
|
||||||
0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07,
|
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75,
|
0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63,
|
||||||
0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22,
|
||||||
0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69,
|
0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18,
|
||||||
0x6f, 0x6e, 0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72,
|
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
|
||||||
0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a,
|
0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79,
|
||||||
0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61,
|
0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49,
|
||||||
0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01,
|
0x64, 0x22, 0x4e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||||
0x28, 0x03, 0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d,
|
0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x73, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65,
|
0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74,
|
||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
|
0x61, 0x22, 0x5a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||||
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xdc, 0x06, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68,
|
0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
|
||||||
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43,
|
||||||
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb4, 0x01,
|
||||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
|
0x0a, 0x1f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75,
|
0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f,
|
0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e,
|
0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74,
|
||||||
0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71,
|
0x61, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
|
0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x50,
|
||||||
0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
|
0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72,
|
||||||
0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
||||||
0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75,
|
0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
0x69, 0x6e, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,
|
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66,
|
||||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e,
|
0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75,
|
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
|
||||||
0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
|
0x66, 0x69, 0x67, 0x22, 0x7d, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65,
|
0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65,
|
||||||
0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
||||||
0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b,
|
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
|
0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
|
||||||
0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
|
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
|
0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
|
||||||
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f,
|
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c,
|
0x49, 0x64, 0x22, 0xb0, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62,
|
||||||
0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
|
0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61,
|
||||||
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
|
0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x62, 0x65, 0x61, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||||
0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69,
|
0x52, 0x0d, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12,
|
||||||
0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d,
|
||||||
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c,
|
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54,
|
||||||
0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04,
|
||||||
0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
|
||||||
0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c,
|
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05,
|
||||||
0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a,
|
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xcd, 0x08, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65,
|
||||||
0x19, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49,
|
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61,
|
||||||
0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61,
|
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
|
||||||
0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b,
|
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65,
|
0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79,
|
||||||
0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79,
|
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68,
|
||||||
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74,
|
0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79,
|
||||||
0x65, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a,
|
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x0c, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e,
|
0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70,
|
0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
|
||||||
0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
|
0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21,
|
||||||
0x31, 0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65,
|
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
|
0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06,
|
0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x12, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72,
|
||||||
|
0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x68, 0x79,
|
||||||
|
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63,
|
||||||
|
0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73,
|
||||||
|
0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||||
|
0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||||
|
0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
|
||||||
|
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65,
|
||||||
|
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c, 0x6f, 0x67,
|
||||||
|
0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
|
0x12, 0x69, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42,
|
||||||
|
0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75,
|
||||||
|
0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67,
|
||||||
|
0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
|
0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x42, 0x6c, 0x6f, 0x63,
|
||||||
|
0x6b, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x43,
|
||||||
|
0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57,
|
||||||
|
0x68, 0x69, 0x74, 0x65, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
||||||
|
0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x6f,
|
||||||
|
0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c, 0x69,
|
||||||
|
0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c,
|
||||||
|
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x69, 0x73, 0x6b, 0x49, 0x50, 0x57, 0x68, 0x69, 0x74, 0x65, 0x6c,
|
||||||
|
0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x47,
|
||||||
|
0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f,
|
||||||
|
0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65,
|
||||||
|
0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
||||||
|
0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73,
|
||||||
|
0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
|
0x7b, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
|
||||||
|
0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x68, 0x79,
|
||||||
|
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61,
|
||||||
|
0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f,
|
||||||
|
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79,
|
||||||
|
0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61,
|
||||||
|
0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x69, 0x73, 0x6b, 0x43, 0x6f,
|
||||||
|
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c,
|
||||||
|
0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x22, 0x2e, 0x68,
|
||||||
|
0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70,
|
||||||
|
0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
|
0x1a, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x6f, 0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75,
|
||||||
|
0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -1538,7 +1861,7 @@ func file_proto_user_v1_auth_proto_rawDescGZIP() []byte {
|
|||||||
return file_proto_user_v1_auth_proto_rawDescData
|
return file_proto_user_v1_auth_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
var file_proto_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
|
||||||
var file_proto_user_v1_auth_proto_goTypes = []any{
|
var file_proto_user_v1_auth_proto_goTypes = []any{
|
||||||
(*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest
|
(*LoginPasswordRequest)(nil), // 0: hyapp.user.v1.LoginPasswordRequest
|
||||||
(*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest
|
(*LoginThirdPartyRequest)(nil), // 1: hyapp.user.v1.LoginThirdPartyRequest
|
||||||
@ -1555,48 +1878,61 @@ var file_proto_user_v1_auth_proto_goTypes = []any{
|
|||||||
(*RecordLoginBlockedResponse)(nil), // 12: hyapp.user.v1.RecordLoginBlockedResponse
|
(*RecordLoginBlockedResponse)(nil), // 12: hyapp.user.v1.RecordLoginBlockedResponse
|
||||||
(*CheckLoginRiskIPWhitelistRequest)(nil), // 13: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
|
(*CheckLoginRiskIPWhitelistRequest)(nil), // 13: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
|
||||||
(*CheckLoginRiskIPWhitelistResponse)(nil), // 14: hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
|
(*CheckLoginRiskIPWhitelistResponse)(nil), // 14: hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
|
||||||
(*AppHeartbeatRequest)(nil), // 15: hyapp.user.v1.AppHeartbeatRequest
|
(*RegisterRiskConfig)(nil), // 15: hyapp.user.v1.RegisterRiskConfig
|
||||||
(*AppHeartbeatResponse)(nil), // 16: hyapp.user.v1.AppHeartbeatResponse
|
(*GetRegisterRiskConfigRequest)(nil), // 16: hyapp.user.v1.GetRegisterRiskConfigRequest
|
||||||
(*RequestMeta)(nil), // 17: hyapp.user.v1.RequestMeta
|
(*GetRegisterRiskConfigResponse)(nil), // 17: hyapp.user.v1.GetRegisterRiskConfigResponse
|
||||||
(*AuthToken)(nil), // 18: hyapp.user.v1.AuthToken
|
(*UpdateRegisterRiskConfigRequest)(nil), // 18: hyapp.user.v1.UpdateRegisterRiskConfigRequest
|
||||||
|
(*UpdateRegisterRiskConfigResponse)(nil), // 19: hyapp.user.v1.UpdateRegisterRiskConfigResponse
|
||||||
|
(*AppHeartbeatRequest)(nil), // 20: hyapp.user.v1.AppHeartbeatRequest
|
||||||
|
(*AppHeartbeatResponse)(nil), // 21: hyapp.user.v1.AppHeartbeatResponse
|
||||||
|
(*RequestMeta)(nil), // 22: hyapp.user.v1.RequestMeta
|
||||||
|
(*AuthToken)(nil), // 23: hyapp.user.v1.AuthToken
|
||||||
}
|
}
|
||||||
var file_proto_user_v1_auth_proto_depIdxs = []int32{
|
var file_proto_user_v1_auth_proto_depIdxs = []int32{
|
||||||
17, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 0: hyapp.user.v1.LoginPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
17, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 1: hyapp.user.v1.LoginThirdPartyRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
18, // 2: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken
|
23, // 2: hyapp.user.v1.AuthResponse.token:type_name -> hyapp.user.v1.AuthToken
|
||||||
17, // 3: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 3: hyapp.user.v1.SetPasswordRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
17, // 4: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 4: hyapp.user.v1.QuickCreateAccountRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
18, // 5: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken
|
23, // 5: hyapp.user.v1.QuickCreateAccountResponse.token:type_name -> hyapp.user.v1.AuthToken
|
||||||
17, // 6: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 6: hyapp.user.v1.RefreshTokenRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
18, // 7: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken
|
23, // 7: hyapp.user.v1.RefreshTokenResponse.token:type_name -> hyapp.user.v1.AuthToken
|
||||||
17, // 8: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 8: hyapp.user.v1.LogoutRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
17, // 9: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 9: hyapp.user.v1.RecordLoginBlockedRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
17, // 10: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 10: hyapp.user.v1.CheckLoginRiskIPWhitelistRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
17, // 11: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
22, // 11: hyapp.user.v1.GetRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
18, // 12: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken
|
15, // 12: hyapp.user.v1.GetRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig
|
||||||
0, // 13: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest
|
22, // 13: hyapp.user.v1.UpdateRegisterRiskConfigRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
1, // 14: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest
|
15, // 14: hyapp.user.v1.UpdateRegisterRiskConfigResponse.config:type_name -> hyapp.user.v1.RegisterRiskConfig
|
||||||
3, // 15: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest
|
22, // 15: hyapp.user.v1.AppHeartbeatRequest.meta:type_name -> hyapp.user.v1.RequestMeta
|
||||||
5, // 16: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest
|
23, // 16: hyapp.user.v1.AppHeartbeatResponse.token:type_name -> hyapp.user.v1.AuthToken
|
||||||
7, // 17: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest
|
0, // 17: hyapp.user.v1.AuthService.LoginPassword:input_type -> hyapp.user.v1.LoginPasswordRequest
|
||||||
9, // 18: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest
|
1, // 18: hyapp.user.v1.AuthService.LoginThirdParty:input_type -> hyapp.user.v1.LoginThirdPartyRequest
|
||||||
11, // 19: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest
|
3, // 19: hyapp.user.v1.AuthService.SetPassword:input_type -> hyapp.user.v1.SetPasswordRequest
|
||||||
13, // 20: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:input_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
|
5, // 20: hyapp.user.v1.AuthService.QuickCreateAccount:input_type -> hyapp.user.v1.QuickCreateAccountRequest
|
||||||
15, // 21: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest
|
7, // 21: hyapp.user.v1.AuthService.RefreshToken:input_type -> hyapp.user.v1.RefreshTokenRequest
|
||||||
2, // 22: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse
|
9, // 22: hyapp.user.v1.AuthService.Logout:input_type -> hyapp.user.v1.LogoutRequest
|
||||||
2, // 23: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse
|
11, // 23: hyapp.user.v1.AuthService.RecordLoginBlocked:input_type -> hyapp.user.v1.RecordLoginBlockedRequest
|
||||||
4, // 24: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse
|
13, // 24: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:input_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistRequest
|
||||||
6, // 25: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse
|
16, // 25: hyapp.user.v1.AuthService.GetRegisterRiskConfig:input_type -> hyapp.user.v1.GetRegisterRiskConfigRequest
|
||||||
8, // 26: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse
|
18, // 26: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:input_type -> hyapp.user.v1.UpdateRegisterRiskConfigRequest
|
||||||
10, // 27: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse
|
20, // 27: hyapp.user.v1.AuthService.AppHeartbeat:input_type -> hyapp.user.v1.AppHeartbeatRequest
|
||||||
12, // 28: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse
|
2, // 28: hyapp.user.v1.AuthService.LoginPassword:output_type -> hyapp.user.v1.AuthResponse
|
||||||
14, // 29: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:output_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
|
2, // 29: hyapp.user.v1.AuthService.LoginThirdParty:output_type -> hyapp.user.v1.AuthResponse
|
||||||
16, // 30: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse
|
4, // 30: hyapp.user.v1.AuthService.SetPassword:output_type -> hyapp.user.v1.SetPasswordResponse
|
||||||
22, // [22:31] is the sub-list for method output_type
|
6, // 31: hyapp.user.v1.AuthService.QuickCreateAccount:output_type -> hyapp.user.v1.QuickCreateAccountResponse
|
||||||
13, // [13:22] is the sub-list for method input_type
|
8, // 32: hyapp.user.v1.AuthService.RefreshToken:output_type -> hyapp.user.v1.RefreshTokenResponse
|
||||||
13, // [13:13] is the sub-list for extension type_name
|
10, // 33: hyapp.user.v1.AuthService.Logout:output_type -> hyapp.user.v1.LogoutResponse
|
||||||
13, // [13:13] is the sub-list for extension extendee
|
12, // 34: hyapp.user.v1.AuthService.RecordLoginBlocked:output_type -> hyapp.user.v1.RecordLoginBlockedResponse
|
||||||
0, // [0:13] is the sub-list for field type_name
|
14, // 35: hyapp.user.v1.AuthService.CheckLoginRiskIPWhitelist:output_type -> hyapp.user.v1.CheckLoginRiskIPWhitelistResponse
|
||||||
|
17, // 36: hyapp.user.v1.AuthService.GetRegisterRiskConfig:output_type -> hyapp.user.v1.GetRegisterRiskConfigResponse
|
||||||
|
19, // 37: hyapp.user.v1.AuthService.UpdateRegisterRiskConfig:output_type -> hyapp.user.v1.UpdateRegisterRiskConfigResponse
|
||||||
|
21, // 38: hyapp.user.v1.AuthService.AppHeartbeat:output_type -> hyapp.user.v1.AppHeartbeatResponse
|
||||||
|
28, // [28:39] is the sub-list for method output_type
|
||||||
|
17, // [17:28] is the sub-list for method input_type
|
||||||
|
17, // [17:17] is the sub-list for extension type_name
|
||||||
|
17, // [17:17] is the sub-list for extension extendee
|
||||||
|
0, // [0:17] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proto_user_v1_auth_proto_init() }
|
func init() { file_proto_user_v1_auth_proto_init() }
|
||||||
@ -1611,7 +1947,7 @@ func file_proto_user_v1_auth_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_proto_user_v1_auth_proto_rawDesc,
|
RawDescriptor: file_proto_user_v1_auth_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 17,
|
NumMessages: 22,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -135,6 +135,34 @@ message CheckLoginRiskIPWhitelistResponse {
|
|||||||
bool whitelisted = 1;
|
bool whitelisted = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterRiskConfig 是注册链路读取的风控配置。
|
||||||
|
message RegisterRiskConfig {
|
||||||
|
string app_code = 1;
|
||||||
|
int32 max_accounts_per_device = 2;
|
||||||
|
int64 updated_at_ms = 3;
|
||||||
|
int64 updated_by_admin_id = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegisterRiskConfigRequest 查询当前 App 的注册风控配置。
|
||||||
|
message GetRegisterRiskConfigRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetRegisterRiskConfigResponse {
|
||||||
|
RegisterRiskConfig config = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRegisterRiskConfigRequest 由后台保存当前 App 的注册风控配置。
|
||||||
|
message UpdateRegisterRiskConfigRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
int32 max_accounts_per_device = 2;
|
||||||
|
int64 operator_admin_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateRegisterRiskConfigResponse {
|
||||||
|
RegisterRiskConfig config = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
|
// AppHeartbeatRequest 刷新当前登录会话的 App 在线心跳。
|
||||||
message AppHeartbeatRequest {
|
message AppHeartbeatRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
@ -160,5 +188,7 @@ service AuthService {
|
|||||||
rpc Logout(LogoutRequest) returns (LogoutResponse);
|
rpc Logout(LogoutRequest) returns (LogoutResponse);
|
||||||
rpc RecordLoginBlocked(RecordLoginBlockedRequest) returns (RecordLoginBlockedResponse);
|
rpc RecordLoginBlocked(RecordLoginBlockedRequest) returns (RecordLoginBlockedResponse);
|
||||||
rpc CheckLoginRiskIPWhitelist(CheckLoginRiskIPWhitelistRequest) returns (CheckLoginRiskIPWhitelistResponse);
|
rpc CheckLoginRiskIPWhitelist(CheckLoginRiskIPWhitelistRequest) returns (CheckLoginRiskIPWhitelistResponse);
|
||||||
|
rpc GetRegisterRiskConfig(GetRegisterRiskConfigRequest) returns (GetRegisterRiskConfigResponse);
|
||||||
|
rpc UpdateRegisterRiskConfig(UpdateRegisterRiskConfigRequest) returns (UpdateRegisterRiskConfigResponse);
|
||||||
rpc AppHeartbeat(AppHeartbeatRequest) returns (AppHeartbeatResponse);
|
rpc AppHeartbeat(AppHeartbeatRequest) returns (AppHeartbeatResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,8 @@ const (
|
|||||||
AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout"
|
AuthService_Logout_FullMethodName = "/hyapp.user.v1.AuthService/Logout"
|
||||||
AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked"
|
AuthService_RecordLoginBlocked_FullMethodName = "/hyapp.user.v1.AuthService/RecordLoginBlocked"
|
||||||
AuthService_CheckLoginRiskIPWhitelist_FullMethodName = "/hyapp.user.v1.AuthService/CheckLoginRiskIPWhitelist"
|
AuthService_CheckLoginRiskIPWhitelist_FullMethodName = "/hyapp.user.v1.AuthService/CheckLoginRiskIPWhitelist"
|
||||||
|
AuthService_GetRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/GetRegisterRiskConfig"
|
||||||
|
AuthService_UpdateRegisterRiskConfig_FullMethodName = "/hyapp.user.v1.AuthService/UpdateRegisterRiskConfig"
|
||||||
AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat"
|
AuthService_AppHeartbeat_FullMethodName = "/hyapp.user.v1.AuthService/AppHeartbeat"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,6 +46,8 @@ type AuthServiceClient interface {
|
|||||||
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error)
|
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error)
|
||||||
RecordLoginBlocked(ctx context.Context, in *RecordLoginBlockedRequest, opts ...grpc.CallOption) (*RecordLoginBlockedResponse, error)
|
RecordLoginBlocked(ctx context.Context, in *RecordLoginBlockedRequest, opts ...grpc.CallOption) (*RecordLoginBlockedResponse, error)
|
||||||
CheckLoginRiskIPWhitelist(ctx context.Context, in *CheckLoginRiskIPWhitelistRequest, opts ...grpc.CallOption) (*CheckLoginRiskIPWhitelistResponse, error)
|
CheckLoginRiskIPWhitelist(ctx context.Context, in *CheckLoginRiskIPWhitelistRequest, opts ...grpc.CallOption) (*CheckLoginRiskIPWhitelistResponse, error)
|
||||||
|
GetRegisterRiskConfig(ctx context.Context, in *GetRegisterRiskConfigRequest, opts ...grpc.CallOption) (*GetRegisterRiskConfigResponse, error)
|
||||||
|
UpdateRegisterRiskConfig(ctx context.Context, in *UpdateRegisterRiskConfigRequest, opts ...grpc.CallOption) (*UpdateRegisterRiskConfigResponse, error)
|
||||||
AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error)
|
AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +139,26 @@ func (c *authServiceClient) CheckLoginRiskIPWhitelist(ctx context.Context, in *C
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *authServiceClient) GetRegisterRiskConfig(ctx context.Context, in *GetRegisterRiskConfigRequest, opts ...grpc.CallOption) (*GetRegisterRiskConfigResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GetRegisterRiskConfigResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AuthService_GetRegisterRiskConfig_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *authServiceClient) UpdateRegisterRiskConfig(ctx context.Context, in *UpdateRegisterRiskConfigRequest, opts ...grpc.CallOption) (*UpdateRegisterRiskConfigResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(UpdateRegisterRiskConfigResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AuthService_UpdateRegisterRiskConfig_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *authServiceClient) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) {
|
func (c *authServiceClient) AppHeartbeat(ctx context.Context, in *AppHeartbeatRequest, opts ...grpc.CallOption) (*AppHeartbeatResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(AppHeartbeatResponse)
|
out := new(AppHeartbeatResponse)
|
||||||
@ -159,6 +183,8 @@ type AuthServiceServer interface {
|
|||||||
Logout(context.Context, *LogoutRequest) (*LogoutResponse, error)
|
Logout(context.Context, *LogoutRequest) (*LogoutResponse, error)
|
||||||
RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error)
|
RecordLoginBlocked(context.Context, *RecordLoginBlockedRequest) (*RecordLoginBlockedResponse, error)
|
||||||
CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error)
|
CheckLoginRiskIPWhitelist(context.Context, *CheckLoginRiskIPWhitelistRequest) (*CheckLoginRiskIPWhitelistResponse, error)
|
||||||
|
GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error)
|
||||||
|
UpdateRegisterRiskConfig(context.Context, *UpdateRegisterRiskConfigRequest) (*UpdateRegisterRiskConfigResponse, error)
|
||||||
AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error)
|
AppHeartbeat(context.Context, *AppHeartbeatRequest) (*AppHeartbeatResponse, error)
|
||||||
mustEmbedUnimplementedAuthServiceServer()
|
mustEmbedUnimplementedAuthServiceServer()
|
||||||
}
|
}
|
||||||
@ -194,6 +220,12 @@ func (UnimplementedAuthServiceServer) RecordLoginBlocked(context.Context, *Recor
|
|||||||
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.Errorf(codes.Unimplemented, "method CheckLoginRiskIPWhitelist not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedAuthServiceServer) GetRegisterRiskConfig(context.Context, *GetRegisterRiskConfigRequest) (*GetRegisterRiskConfigResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetRegisterRiskConfig not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAuthServiceServer) UpdateRegisterRiskConfig(context.Context, *UpdateRegisterRiskConfigRequest) (*UpdateRegisterRiskConfigResponse, error) {
|
||||||
|
return nil, status.Errorf(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.Errorf(codes.Unimplemented, "method AppHeartbeat not implemented")
|
||||||
}
|
}
|
||||||
@ -362,6 +394,42 @@ func _AuthService_CheckLoginRiskIPWhitelist_Handler(srv interface{}, ctx context
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _AuthService_GetRegisterRiskConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetRegisterRiskConfigRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AuthServiceServer).GetRegisterRiskConfig(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AuthService_GetRegisterRiskConfig_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AuthServiceServer).GetRegisterRiskConfig(ctx, req.(*GetRegisterRiskConfigRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AuthService_UpdateRegisterRiskConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UpdateRegisterRiskConfigRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AuthServiceServer).UpdateRegisterRiskConfig(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AuthService_UpdateRegisterRiskConfig_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AuthServiceServer).UpdateRegisterRiskConfig(ctx, req.(*UpdateRegisterRiskConfigRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _AuthService_AppHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _AuthService_AppHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(AppHeartbeatRequest)
|
in := new(AppHeartbeatRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -419,6 +487,14 @@ var AuthService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "CheckLoginRiskIPWhitelist",
|
MethodName: "CheckLoginRiskIPWhitelist",
|
||||||
Handler: _AuthService_CheckLoginRiskIPWhitelist_Handler,
|
Handler: _AuthService_CheckLoginRiskIPWhitelist_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetRegisterRiskConfig",
|
||||||
|
Handler: _AuthService_GetRegisterRiskConfig_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UpdateRegisterRiskConfig",
|
||||||
|
Handler: _AuthService_UpdateRegisterRiskConfig_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "AppHeartbeat",
|
MethodName: "AppHeartbeat",
|
||||||
Handler: _AuthService_AppHeartbeat_Handler,
|
Handler: _AuthService_AppHeartbeat_Handler,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -277,6 +277,16 @@ message ListRoleInvitationsResponse {
|
|||||||
repeated RoleInvitation invitations = 1;
|
repeated RoleInvitation invitations = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetRoleInvitationRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
int64 actor_user_id = 2;
|
||||||
|
int64 invitation_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetRoleInvitationResponse {
|
||||||
|
RoleInvitation invitation = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ProcessRoleInvitationRequest {
|
message ProcessRoleInvitationRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
string command_id = 2;
|
string command_id = 2;
|
||||||
@ -513,6 +523,7 @@ service UserHostService {
|
|||||||
rpc ListBDLeaderAgencies(ListBDLeaderAgenciesRequest) returns (ListBDLeaderAgenciesResponse);
|
rpc ListBDLeaderAgencies(ListBDLeaderAgenciesRequest) returns (ListBDLeaderAgenciesResponse);
|
||||||
rpc ListBDAgencies(ListBDAgenciesRequest) returns (ListBDAgenciesResponse);
|
rpc ListBDAgencies(ListBDAgenciesRequest) returns (ListBDAgenciesResponse);
|
||||||
rpc ListRoleInvitations(ListRoleInvitationsRequest) returns (ListRoleInvitationsResponse);
|
rpc ListRoleInvitations(ListRoleInvitationsRequest) returns (ListRoleInvitationsResponse);
|
||||||
|
rpc GetRoleInvitation(GetRoleInvitationRequest) returns (GetRoleInvitationResponse);
|
||||||
rpc ProcessRoleInvitation(ProcessRoleInvitationRequest) returns (ProcessRoleInvitationResponse);
|
rpc ProcessRoleInvitation(ProcessRoleInvitationRequest) returns (ProcessRoleInvitationResponse);
|
||||||
rpc GetHostProfile(GetHostProfileRequest) returns (GetHostProfileResponse);
|
rpc GetHostProfile(GetHostProfileRequest) returns (GetHostProfileResponse);
|
||||||
rpc GetBDProfile(GetBDProfileRequest) returns (GetBDProfileResponse);
|
rpc GetBDProfile(GetBDProfileRequest) returns (GetBDProfileResponse);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const (
|
|||||||
UserHostService_ListBDLeaderAgencies_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDLeaderAgencies"
|
UserHostService_ListBDLeaderAgencies_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDLeaderAgencies"
|
||||||
UserHostService_ListBDAgencies_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDAgencies"
|
UserHostService_ListBDAgencies_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDAgencies"
|
||||||
UserHostService_ListRoleInvitations_FullMethodName = "/hyapp.user.v1.UserHostService/ListRoleInvitations"
|
UserHostService_ListRoleInvitations_FullMethodName = "/hyapp.user.v1.UserHostService/ListRoleInvitations"
|
||||||
|
UserHostService_GetRoleInvitation_FullMethodName = "/hyapp.user.v1.UserHostService/GetRoleInvitation"
|
||||||
UserHostService_ProcessRoleInvitation_FullMethodName = "/hyapp.user.v1.UserHostService/ProcessRoleInvitation"
|
UserHostService_ProcessRoleInvitation_FullMethodName = "/hyapp.user.v1.UserHostService/ProcessRoleInvitation"
|
||||||
UserHostService_GetHostProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetHostProfile"
|
UserHostService_GetHostProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetHostProfile"
|
||||||
UserHostService_GetBDProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetBDProfile"
|
UserHostService_GetBDProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetBDProfile"
|
||||||
@ -59,6 +60,7 @@ type UserHostServiceClient interface {
|
|||||||
ListBDLeaderAgencies(ctx context.Context, in *ListBDLeaderAgenciesRequest, opts ...grpc.CallOption) (*ListBDLeaderAgenciesResponse, error)
|
ListBDLeaderAgencies(ctx context.Context, in *ListBDLeaderAgenciesRequest, opts ...grpc.CallOption) (*ListBDLeaderAgenciesResponse, error)
|
||||||
ListBDAgencies(ctx context.Context, in *ListBDAgenciesRequest, opts ...grpc.CallOption) (*ListBDAgenciesResponse, error)
|
ListBDAgencies(ctx context.Context, in *ListBDAgenciesRequest, opts ...grpc.CallOption) (*ListBDAgenciesResponse, error)
|
||||||
ListRoleInvitations(ctx context.Context, in *ListRoleInvitationsRequest, opts ...grpc.CallOption) (*ListRoleInvitationsResponse, error)
|
ListRoleInvitations(ctx context.Context, in *ListRoleInvitationsRequest, opts ...grpc.CallOption) (*ListRoleInvitationsResponse, error)
|
||||||
|
GetRoleInvitation(ctx context.Context, in *GetRoleInvitationRequest, opts ...grpc.CallOption) (*GetRoleInvitationResponse, error)
|
||||||
ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error)
|
ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error)
|
||||||
GetHostProfile(ctx context.Context, in *GetHostProfileRequest, opts ...grpc.CallOption) (*GetHostProfileResponse, error)
|
GetHostProfile(ctx context.Context, in *GetHostProfileRequest, opts ...grpc.CallOption) (*GetHostProfileResponse, error)
|
||||||
GetBDProfile(ctx context.Context, in *GetBDProfileRequest, opts ...grpc.CallOption) (*GetBDProfileResponse, error)
|
GetBDProfile(ctx context.Context, in *GetBDProfileRequest, opts ...grpc.CallOption) (*GetBDProfileResponse, error)
|
||||||
@ -189,6 +191,16 @@ func (c *userHostServiceClient) ListRoleInvitations(ctx context.Context, in *Lis
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *userHostServiceClient) GetRoleInvitation(ctx context.Context, in *GetRoleInvitationRequest, opts ...grpc.CallOption) (*GetRoleInvitationResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GetRoleInvitationResponse)
|
||||||
|
err := c.cc.Invoke(ctx, UserHostService_GetRoleInvitation_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *userHostServiceClient) ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error) {
|
func (c *userHostServiceClient) ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ProcessRoleInvitationResponse)
|
out := new(ProcessRoleInvitationResponse)
|
||||||
@ -306,6 +318,7 @@ type UserHostServiceServer interface {
|
|||||||
ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error)
|
ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error)
|
||||||
ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error)
|
ListBDAgencies(context.Context, *ListBDAgenciesRequest) (*ListBDAgenciesResponse, error)
|
||||||
ListRoleInvitations(context.Context, *ListRoleInvitationsRequest) (*ListRoleInvitationsResponse, error)
|
ListRoleInvitations(context.Context, *ListRoleInvitationsRequest) (*ListRoleInvitationsResponse, error)
|
||||||
|
GetRoleInvitation(context.Context, *GetRoleInvitationRequest) (*GetRoleInvitationResponse, error)
|
||||||
ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error)
|
ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error)
|
||||||
GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error)
|
GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error)
|
||||||
GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error)
|
GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error)
|
||||||
@ -359,6 +372,9 @@ func (UnimplementedUserHostServiceServer) ListBDAgencies(context.Context, *ListB
|
|||||||
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.Errorf(codes.Unimplemented, "method ListRoleInvitations not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedUserHostServiceServer) GetRoleInvitation(context.Context, *GetRoleInvitationRequest) (*GetRoleInvitationResponse, error) {
|
||||||
|
return nil, status.Errorf(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.Errorf(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
|
||||||
}
|
}
|
||||||
@ -608,6 +624,24 @@ func _UserHostService_ListRoleInvitations_Handler(srv interface{}, ctx context.C
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _UserHostService_GetRoleInvitation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetRoleInvitationRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserHostServiceServer).GetRoleInvitation(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: UserHostService_GetRoleInvitation_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserHostServiceServer).GetRoleInvitation(ctx, req.(*GetRoleInvitationRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _UserHostService_ProcessRoleInvitation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _UserHostService_ProcessRoleInvitation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ProcessRoleInvitationRequest)
|
in := new(ProcessRoleInvitationRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -839,6 +873,10 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ListRoleInvitations",
|
MethodName: "ListRoleInvitations",
|
||||||
Handler: _UserHostService_ListRoleInvitations_Handler,
|
Handler: _UserHostService_ListRoleInvitations_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetRoleInvitation",
|
||||||
|
Handler: _UserHostService_GetRoleInvitation_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ProcessRoleInvitation",
|
MethodName: "ProcessRoleInvitation",
|
||||||
Handler: _UserHostService_ProcessRoleInvitation_Handler,
|
Handler: _UserHostService_ProcessRoleInvitation_Handler,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -212,7 +212,7 @@ message AdminCreditAssetResponse {
|
|||||||
AssetBalance balance = 2;
|
AssetBalance balance = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminCreditCoinSellerStockRequest 是后台给 active 币商专用库存入账的账务命令。
|
// AdminCreditCoinSellerStockRequest 是后台调整币商专用库存的账务命令。
|
||||||
message AdminCreditCoinSellerStockRequest {
|
message AdminCreditCoinSellerStockRequest {
|
||||||
string command_id = 1;
|
string command_id = 1;
|
||||||
int64 seller_user_id = 2;
|
int64 seller_user_id = 2;
|
||||||
@ -494,6 +494,9 @@ message ResourceGrant {
|
|||||||
repeated ResourceGrantItem items = 11;
|
repeated ResourceGrantItem items = 11;
|
||||||
int64 created_at_ms = 12;
|
int64 created_at_ms = 12;
|
||||||
int64 updated_at_ms = 13;
|
int64 updated_at_ms = 13;
|
||||||
|
int64 revoked_at_ms = 14;
|
||||||
|
int64 revoked_by_user_id = 15;
|
||||||
|
string revoke_reason = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResourceGroupItemInput {
|
message ResourceGroupItemInput {
|
||||||
@ -793,6 +796,14 @@ message GrantResourceGroupRequest {
|
|||||||
string grant_source = 7;
|
string grant_source = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RevokeResourceGrantRequest {
|
||||||
|
string request_id = 1;
|
||||||
|
string app_code = 2;
|
||||||
|
string grant_id = 3;
|
||||||
|
string reason = 4;
|
||||||
|
int64 operator_user_id = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message ResourceGrantResponse {
|
message ResourceGrantResponse {
|
||||||
ResourceGrant grant = 1;
|
ResourceGrant grant = 1;
|
||||||
}
|
}
|
||||||
@ -1305,6 +1316,44 @@ message CreateH5RechargeOrderRequest {
|
|||||||
string payer_email = 17;
|
string payer_email = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CreateTemporaryRechargeOrderRequest {
|
||||||
|
string request_id = 1;
|
||||||
|
string app_code = 2;
|
||||||
|
string command_id = 3;
|
||||||
|
int64 coin_amount = 4;
|
||||||
|
int64 usd_minor_amount = 5;
|
||||||
|
string provider_code = 6;
|
||||||
|
int64 payment_method_id = 7;
|
||||||
|
string return_url = 8;
|
||||||
|
string notify_url = 9;
|
||||||
|
string client_ip = 10;
|
||||||
|
string language = 11;
|
||||||
|
string payer_name = 12;
|
||||||
|
string payer_account = 13;
|
||||||
|
string payer_email = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetTemporaryRechargeOrderRequest {
|
||||||
|
string request_id = 1;
|
||||||
|
string app_code = 2;
|
||||||
|
string order_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTemporaryRechargeOrdersRequest {
|
||||||
|
string request_id = 1;
|
||||||
|
string app_code = 2;
|
||||||
|
string status = 3;
|
||||||
|
string provider_code = 4;
|
||||||
|
string keyword = 5;
|
||||||
|
int32 page = 6;
|
||||||
|
int32 page_size = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListTemporaryRechargeOrdersResponse {
|
||||||
|
repeated ExternalRechargeOrder orders = 1;
|
||||||
|
int64 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message SubmitH5RechargeTxRequest {
|
message SubmitH5RechargeTxRequest {
|
||||||
string request_id = 1;
|
string request_id = 1;
|
||||||
string app_code = 2;
|
string app_code = 2;
|
||||||
@ -1989,6 +2038,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 RevokeResourceGrant(RevokeResourceGrantRequest) returns (ResourceGrantResponse);
|
||||||
rpc ListUserResources(ListUserResourcesRequest) returns (ListUserResourcesResponse);
|
rpc ListUserResources(ListUserResourcesRequest) returns (ListUserResourcesResponse);
|
||||||
rpc EquipUserResource(EquipUserResourceRequest) returns (EquipUserResourceResponse);
|
rpc EquipUserResource(EquipUserResourceRequest) returns (EquipUserResourceResponse);
|
||||||
rpc UnequipUserResource(UnequipUserResourceRequest) returns (UnequipUserResourceResponse);
|
rpc UnequipUserResource(UnequipUserResourceRequest) returns (UnequipUserResourceResponse);
|
||||||
@ -2014,6 +2064,9 @@ service WalletService {
|
|||||||
rpc UpdateThirdPartyPaymentRate(UpdateThirdPartyPaymentRateRequest) returns (ThirdPartyPaymentMethodResponse);
|
rpc UpdateThirdPartyPaymentRate(UpdateThirdPartyPaymentRateRequest) returns (ThirdPartyPaymentMethodResponse);
|
||||||
rpc ListH5RechargeOptions(H5RechargeOptionsRequest) returns (H5RechargeOptionsResponse);
|
rpc ListH5RechargeOptions(H5RechargeOptionsRequest) returns (H5RechargeOptionsResponse);
|
||||||
rpc CreateH5RechargeOrder(CreateH5RechargeOrderRequest) returns (H5RechargeOrderResponse);
|
rpc CreateH5RechargeOrder(CreateH5RechargeOrderRequest) returns (H5RechargeOrderResponse);
|
||||||
|
rpc CreateTemporaryRechargeOrder(CreateTemporaryRechargeOrderRequest) returns (H5RechargeOrderResponse);
|
||||||
|
rpc GetTemporaryRechargeOrder(GetTemporaryRechargeOrderRequest) returns (H5RechargeOrderResponse);
|
||||||
|
rpc ListTemporaryRechargeOrders(ListTemporaryRechargeOrdersRequest) returns (ListTemporaryRechargeOrdersResponse);
|
||||||
rpc SubmitH5RechargeTx(SubmitH5RechargeTxRequest) returns (H5RechargeOrderResponse);
|
rpc SubmitH5RechargeTx(SubmitH5RechargeTxRequest) returns (H5RechargeOrderResponse);
|
||||||
rpc GetH5RechargeOrder(GetH5RechargeOrderRequest) returns (H5RechargeOrderResponse);
|
rpc GetH5RechargeOrder(GetH5RechargeOrderRequest) returns (H5RechargeOrderResponse);
|
||||||
rpc HandleMifapayNotify(HandleMifapayNotifyRequest) returns (HandleMifapayNotifyResponse);
|
rpc HandleMifapayNotify(HandleMifapayNotifyRequest) returns (HandleMifapayNotifyResponse);
|
||||||
|
|||||||
@ -232,6 +232,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_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"
|
||||||
WalletService_UnequipUserResource_FullMethodName = "/hyapp.wallet.v1.WalletService/UnequipUserResource"
|
WalletService_UnequipUserResource_FullMethodName = "/hyapp.wallet.v1.WalletService/UnequipUserResource"
|
||||||
@ -257,6 +258,9 @@ const (
|
|||||||
WalletService_UpdateThirdPartyPaymentRate_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateThirdPartyPaymentRate"
|
WalletService_UpdateThirdPartyPaymentRate_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateThirdPartyPaymentRate"
|
||||||
WalletService_ListH5RechargeOptions_FullMethodName = "/hyapp.wallet.v1.WalletService/ListH5RechargeOptions"
|
WalletService_ListH5RechargeOptions_FullMethodName = "/hyapp.wallet.v1.WalletService/ListH5RechargeOptions"
|
||||||
WalletService_CreateH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateH5RechargeOrder"
|
WalletService_CreateH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateH5RechargeOrder"
|
||||||
|
WalletService_CreateTemporaryRechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/CreateTemporaryRechargeOrder"
|
||||||
|
WalletService_GetTemporaryRechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/GetTemporaryRechargeOrder"
|
||||||
|
WalletService_ListTemporaryRechargeOrders_FullMethodName = "/hyapp.wallet.v1.WalletService/ListTemporaryRechargeOrders"
|
||||||
WalletService_SubmitH5RechargeTx_FullMethodName = "/hyapp.wallet.v1.WalletService/SubmitH5RechargeTx"
|
WalletService_SubmitH5RechargeTx_FullMethodName = "/hyapp.wallet.v1.WalletService/SubmitH5RechargeTx"
|
||||||
WalletService_GetH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/GetH5RechargeOrder"
|
WalletService_GetH5RechargeOrder_FullMethodName = "/hyapp.wallet.v1.WalletService/GetH5RechargeOrder"
|
||||||
WalletService_HandleMifapayNotify_FullMethodName = "/hyapp.wallet.v1.WalletService/HandleMifapayNotify"
|
WalletService_HandleMifapayNotify_FullMethodName = "/hyapp.wallet.v1.WalletService/HandleMifapayNotify"
|
||||||
@ -325,6 +329,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)
|
||||||
|
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)
|
||||||
UnequipUserResource(ctx context.Context, in *UnequipUserResourceRequest, opts ...grpc.CallOption) (*UnequipUserResourceResponse, error)
|
UnequipUserResource(ctx context.Context, in *UnequipUserResourceRequest, opts ...grpc.CallOption) (*UnequipUserResourceResponse, error)
|
||||||
@ -350,6 +355,9 @@ type WalletServiceClient interface {
|
|||||||
UpdateThirdPartyPaymentRate(ctx context.Context, in *UpdateThirdPartyPaymentRateRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error)
|
UpdateThirdPartyPaymentRate(ctx context.Context, in *UpdateThirdPartyPaymentRateRequest, opts ...grpc.CallOption) (*ThirdPartyPaymentMethodResponse, error)
|
||||||
ListH5RechargeOptions(ctx context.Context, in *H5RechargeOptionsRequest, opts ...grpc.CallOption) (*H5RechargeOptionsResponse, error)
|
ListH5RechargeOptions(ctx context.Context, in *H5RechargeOptionsRequest, opts ...grpc.CallOption) (*H5RechargeOptionsResponse, error)
|
||||||
CreateH5RechargeOrder(ctx context.Context, in *CreateH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
CreateH5RechargeOrder(ctx context.Context, in *CreateH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
||||||
|
CreateTemporaryRechargeOrder(ctx context.Context, in *CreateTemporaryRechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
||||||
|
GetTemporaryRechargeOrder(ctx context.Context, in *GetTemporaryRechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
||||||
|
ListTemporaryRechargeOrders(ctx context.Context, in *ListTemporaryRechargeOrdersRequest, opts ...grpc.CallOption) (*ListTemporaryRechargeOrdersResponse, error)
|
||||||
SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
||||||
GetH5RechargeOrder(ctx context.Context, in *GetH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
GetH5RechargeOrder(ctx context.Context, in *GetH5RechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error)
|
||||||
HandleMifapayNotify(ctx context.Context, in *HandleMifapayNotifyRequest, opts ...grpc.CallOption) (*HandleMifapayNotifyResponse, error)
|
HandleMifapayNotify(ctx context.Context, in *HandleMifapayNotifyRequest, opts ...grpc.CallOption) (*HandleMifapayNotifyResponse, error)
|
||||||
@ -699,6 +707,16 @@ func (c *walletServiceClient) GrantResourceGroup(ctx context.Context, in *GrantR
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *walletServiceClient) RevokeResourceGrant(ctx context.Context, in *RevokeResourceGrantRequest, opts ...grpc.CallOption) (*ResourceGrantResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ResourceGrantResponse)
|
||||||
|
err := c.cc.Invoke(ctx, WalletService_RevokeResourceGrant_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *walletServiceClient) ListUserResources(ctx context.Context, in *ListUserResourcesRequest, opts ...grpc.CallOption) (*ListUserResourcesResponse, error) {
|
func (c *walletServiceClient) ListUserResources(ctx context.Context, in *ListUserResourcesRequest, opts ...grpc.CallOption) (*ListUserResourcesResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ListUserResourcesResponse)
|
out := new(ListUserResourcesResponse)
|
||||||
@ -949,6 +967,36 @@ func (c *walletServiceClient) CreateH5RechargeOrder(ctx context.Context, in *Cre
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *walletServiceClient) CreateTemporaryRechargeOrder(ctx context.Context, in *CreateTemporaryRechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(H5RechargeOrderResponse)
|
||||||
|
err := c.cc.Invoke(ctx, WalletService_CreateTemporaryRechargeOrder_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *walletServiceClient) GetTemporaryRechargeOrder(ctx context.Context, in *GetTemporaryRechargeOrderRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(H5RechargeOrderResponse)
|
||||||
|
err := c.cc.Invoke(ctx, WalletService_GetTemporaryRechargeOrder_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *walletServiceClient) ListTemporaryRechargeOrders(ctx context.Context, in *ListTemporaryRechargeOrdersRequest, opts ...grpc.CallOption) (*ListTemporaryRechargeOrdersResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListTemporaryRechargeOrdersResponse)
|
||||||
|
err := c.cc.Invoke(ctx, WalletService_ListTemporaryRechargeOrders_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *walletServiceClient) SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) {
|
func (c *walletServiceClient) SubmitH5RechargeTx(ctx context.Context, in *SubmitH5RechargeTxRequest, opts ...grpc.CallOption) (*H5RechargeOrderResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(H5RechargeOrderResponse)
|
out := new(H5RechargeOrderResponse)
|
||||||
@ -1276,6 +1324,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)
|
||||||
|
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)
|
||||||
UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error)
|
UnequipUserResource(context.Context, *UnequipUserResourceRequest) (*UnequipUserResourceResponse, error)
|
||||||
@ -1301,6 +1350,9 @@ type WalletServiceServer interface {
|
|||||||
UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error)
|
UpdateThirdPartyPaymentRate(context.Context, *UpdateThirdPartyPaymentRateRequest) (*ThirdPartyPaymentMethodResponse, error)
|
||||||
ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error)
|
ListH5RechargeOptions(context.Context, *H5RechargeOptionsRequest) (*H5RechargeOptionsResponse, error)
|
||||||
CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
CreateH5RechargeOrder(context.Context, *CreateH5RechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
||||||
|
CreateTemporaryRechargeOrder(context.Context, *CreateTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
||||||
|
GetTemporaryRechargeOrder(context.Context, *GetTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
||||||
|
ListTemporaryRechargeOrders(context.Context, *ListTemporaryRechargeOrdersRequest) (*ListTemporaryRechargeOrdersResponse, error)
|
||||||
SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error)
|
SubmitH5RechargeTx(context.Context, *SubmitH5RechargeTxRequest) (*H5RechargeOrderResponse, error)
|
||||||
GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
GetH5RechargeOrder(context.Context, *GetH5RechargeOrderRequest) (*H5RechargeOrderResponse, error)
|
||||||
HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error)
|
HandleMifapayNotify(context.Context, *HandleMifapayNotifyRequest) (*HandleMifapayNotifyResponse, error)
|
||||||
@ -1433,6 +1485,9 @@ func (UnimplementedWalletServiceServer) GrantResource(context.Context, *GrantRes
|
|||||||
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.Errorf(codes.Unimplemented, "method GrantResourceGroup not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedWalletServiceServer) RevokeResourceGrant(context.Context, *RevokeResourceGrantRequest) (*ResourceGrantResponse, error) {
|
||||||
|
return nil, status.Errorf(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.Errorf(codes.Unimplemented, "method ListUserResources not implemented")
|
||||||
}
|
}
|
||||||
@ -1508,6 +1563,15 @@ func (UnimplementedWalletServiceServer) ListH5RechargeOptions(context.Context, *
|
|||||||
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.Errorf(codes.Unimplemented, "method CreateH5RechargeOrder not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedWalletServiceServer) CreateTemporaryRechargeOrder(context.Context, *CreateTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreateTemporaryRechargeOrder not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedWalletServiceServer) GetTemporaryRechargeOrder(context.Context, *GetTemporaryRechargeOrderRequest) (*H5RechargeOrderResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetTemporaryRechargeOrder not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedWalletServiceServer) ListTemporaryRechargeOrders(context.Context, *ListTemporaryRechargeOrdersRequest) (*ListTemporaryRechargeOrdersResponse, error) {
|
||||||
|
return nil, status.Errorf(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.Errorf(codes.Unimplemented, "method SubmitH5RechargeTx not implemented")
|
||||||
}
|
}
|
||||||
@ -2174,6 +2238,24 @@ func _WalletService_GrantResourceGroup_Handler(srv interface{}, ctx context.Cont
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _WalletService_RevokeResourceGrant_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RevokeResourceGrantRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletServiceServer).RevokeResourceGrant(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: WalletService_RevokeResourceGrant_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletServiceServer).RevokeResourceGrant(ctx, req.(*RevokeResourceGrantRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _WalletService_ListUserResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _WalletService_ListUserResources_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ListUserResourcesRequest)
|
in := new(ListUserResourcesRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -2624,6 +2706,60 @@ func _WalletService_CreateH5RechargeOrder_Handler(srv interface{}, ctx context.C
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _WalletService_CreateTemporaryRechargeOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CreateTemporaryRechargeOrderRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletServiceServer).CreateTemporaryRechargeOrder(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: WalletService_CreateTemporaryRechargeOrder_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletServiceServer).CreateTemporaryRechargeOrder(ctx, req.(*CreateTemporaryRechargeOrderRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _WalletService_GetTemporaryRechargeOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetTemporaryRechargeOrderRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletServiceServer).GetTemporaryRechargeOrder(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: WalletService_GetTemporaryRechargeOrder_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletServiceServer).GetTemporaryRechargeOrder(ctx, req.(*GetTemporaryRechargeOrderRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _WalletService_ListTemporaryRechargeOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListTemporaryRechargeOrdersRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletServiceServer).ListTemporaryRechargeOrders(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: WalletService_ListTemporaryRechargeOrders_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletServiceServer).ListTemporaryRechargeOrders(ctx, req.(*ListTemporaryRechargeOrdersRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _WalletService_SubmitH5RechargeTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _WalletService_SubmitH5RechargeTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(SubmitH5RechargeTxRequest)
|
in := new(SubmitH5RechargeTxRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -3277,6 +3413,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GrantResourceGroup",
|
MethodName: "GrantResourceGroup",
|
||||||
Handler: _WalletService_GrantResourceGroup_Handler,
|
Handler: _WalletService_GrantResourceGroup_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RevokeResourceGrant",
|
||||||
|
Handler: _WalletService_RevokeResourceGrant_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ListUserResources",
|
MethodName: "ListUserResources",
|
||||||
Handler: _WalletService_ListUserResources_Handler,
|
Handler: _WalletService_ListUserResources_Handler,
|
||||||
@ -3377,6 +3517,18 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "CreateH5RechargeOrder",
|
MethodName: "CreateH5RechargeOrder",
|
||||||
Handler: _WalletService_CreateH5RechargeOrder_Handler,
|
Handler: _WalletService_CreateH5RechargeOrder_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CreateTemporaryRechargeOrder",
|
||||||
|
Handler: _WalletService_CreateTemporaryRechargeOrder_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetTemporaryRechargeOrder",
|
||||||
|
Handler: _WalletService_GetTemporaryRechargeOrder_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListTemporaryRechargeOrders",
|
||||||
|
Handler: _WalletService_ListTemporaryRechargeOrders_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "SubmitH5RechargeTx",
|
MethodName: "SubmitH5RechargeTx",
|
||||||
Handler: _WalletService_SubmitH5RechargeTx_Handler,
|
Handler: _WalletService_SubmitH5RechargeTx_Handler,
|
||||||
|
|||||||
477
docs/flutter对接/通用确认消息Flutter对接.md
Normal file
477
docs/flutter对接/通用确认消息Flutter对接.md
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
# 通用确认消息 Flutter 对接
|
||||||
|
|
||||||
|
本文定义 Flutter 对接 `im_confirm` 私聊确认消息所需的 IM 解析、HTTP 接口、状态刷新和按钮置灰规则。业务类型首版是 `host / agency / bd` 身份邀请,Flutter 只做通用展示,不写业务分支。
|
||||||
|
|
||||||
|
## 通用约定
|
||||||
|
|
||||||
|
### Base URL
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://{api_host}
|
||||||
|
```
|
||||||
|
|
||||||
|
本地开发:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://127.0.0.1:13000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
| Header | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Authorization: Bearer {access_token}` | 是 | 登录后的 App token |
|
||||||
|
| `Content-Type: application/json` | POST 必填 | 请求体 JSON |
|
||||||
|
| `X-App-Code` | 否 | 多 App 包场景可传,通常由 gateway 解析 |
|
||||||
|
|
||||||
|
### 响应 Envelope
|
||||||
|
|
||||||
|
成功:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "OK",
|
||||||
|
"message": "ok",
|
||||||
|
"request_id": "req_xxx",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "CONFLICT",
|
||||||
|
"message": "conflict",
|
||||||
|
"request_id": "req_xxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## IM 消息格式
|
||||||
|
|
||||||
|
腾讯 IM C2C 自定义消息:
|
||||||
|
|
||||||
|
| TIM 字段 | 值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `Desc` | `im_confirm` |
|
||||||
|
| `Ext` | `im_confirm` |
|
||||||
|
| `Data` | JSON 字符串 |
|
||||||
|
|
||||||
|
`Data` 示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "im_confirm",
|
||||||
|
"version": 1,
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"msg": "Alice has invited you to join ABC guild",
|
||||||
|
"status": "pending",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "reject",
|
||||||
|
"label": "Reject",
|
||||||
|
"style": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"label": "Accept",
|
||||||
|
"style": "primary"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at_ms": 1710000000000,
|
||||||
|
"expire_at_ms": 1710604800000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `type` | string | 固定 `im_confirm` |
|
||||||
|
| `version` | int | 当前为 `1` |
|
||||||
|
| `confirm_id` | string | 确认消息 ID,后续接口只传这个 |
|
||||||
|
| `business_type` | string | 业务类型,只用于日志和埋点 |
|
||||||
|
| `business_subtype` | string | 业务子类型,只用于展示或埋点 |
|
||||||
|
| `msg` | string | 气泡文案 |
|
||||||
|
| `status` | string | 初始状态,一般为 `pending` |
|
||||||
|
| `actions` | array | 按钮配置 |
|
||||||
|
| `created_at_ms` | int64 | 创建时间 |
|
||||||
|
| `expire_at_ms` | int64 | 过期时间,可能为 0 |
|
||||||
|
|
||||||
|
Flutter 不要把 `business_type`、`business_subtype` 当成接口参数。确认接口只需要 `confirm_id`。
|
||||||
|
|
||||||
|
## 状态含义
|
||||||
|
|
||||||
|
| 状态 | Flutter 展示 |
|
||||||
|
| --- | --- |
|
||||||
|
| `pending` | 展示可点击按钮 |
|
||||||
|
| `processing` | 按钮置灰,显示处理中或本地 loading |
|
||||||
|
| `accepted` | 按钮置灰,接受态 |
|
||||||
|
| `rejected` | 按钮置灰,拒绝态 |
|
||||||
|
| `cancelled` | 按钮置灰,已取消 |
|
||||||
|
| `expired` | 按钮置灰,已过期 |
|
||||||
|
| `failed` | 按钮置灰,可提示稍后重试或联系客服 |
|
||||||
|
|
||||||
|
按钮是否可点只看最新状态是否为 `pending`。
|
||||||
|
|
||||||
|
## 接口 1:接受
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/{confirm_id}/accept
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command_id": "cfm_123_accept_001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "accepted",
|
||||||
|
"processed_action": "accept",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:用户点击 `actions.action=accept` 的按钮时调用。
|
||||||
|
|
||||||
|
## 接口 2:拒绝
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/{confirm_id}/reject
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command_id": "cfm_123_reject_001",
|
||||||
|
"reason": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "rejected",
|
||||||
|
"processed_action": "reject",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:用户点击 `actions.action=reject` 的按钮时调用。
|
||||||
|
|
||||||
|
## 接口 3:批量刷新状态
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/status
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_ids": ["cfm_123", "cfm_456"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "accepted",
|
||||||
|
"processed_action": "accept",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
使用场景:
|
||||||
|
|
||||||
|
- 打开会话时,收集当前会话里所有 `im_confirm.confirm_id` 后批量刷新。
|
||||||
|
- App 从后台回到前台时,刷新当前可见页面的确认消息状态。
|
||||||
|
- 接口返回的状态覆盖 IM 初始 `status`。
|
||||||
|
|
||||||
|
## 接口 4:按会话刷新状态
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/v1/message/action-confirms?peer_user_id=10001&page_size=50
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
| 参数 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `peer_user_id` | 是 | 当前会话对方用户 ID |
|
||||||
|
| `page_size` | 否 | 默认 50,最大 100 |
|
||||||
|
| `page_token` | 否 | 下一页游标 |
|
||||||
|
|
||||||
|
返回值 `data`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"sender_user_id": "10001",
|
||||||
|
"receiver_user_id": "10002",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "pending",
|
||||||
|
"processed_action": "",
|
||||||
|
"processed_at_ms": 0,
|
||||||
|
"created_at_ms": 1710000000000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_page_token": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
使用场景:
|
||||||
|
|
||||||
|
- 当前 IM SDK 本地消息无法快速遍历 `confirm_id` 时使用。
|
||||||
|
- 当前用户由 token 决定,Flutter 只传对方 `peer_user_id`。
|
||||||
|
|
||||||
|
## Flutter 数据模型
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class ImConfirmMessage {
|
||||||
|
final String confirmId;
|
||||||
|
final String businessType;
|
||||||
|
final String businessSubtype;
|
||||||
|
final String msg;
|
||||||
|
final String status;
|
||||||
|
final List<ConfirmAction> actions;
|
||||||
|
final int createdAtMs;
|
||||||
|
final int expireAtMs;
|
||||||
|
|
||||||
|
ImConfirmMessage({
|
||||||
|
required this.confirmId,
|
||||||
|
required this.businessType,
|
||||||
|
required this.businessSubtype,
|
||||||
|
required this.msg,
|
||||||
|
required this.status,
|
||||||
|
required this.actions,
|
||||||
|
required this.createdAtMs,
|
||||||
|
required this.expireAtMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool get canOperate => status == 'pending';
|
||||||
|
|
||||||
|
factory ImConfirmMessage.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ImConfirmMessage(
|
||||||
|
confirmId: (json['confirm_id'] ?? '').toString(),
|
||||||
|
businessType: (json['business_type'] ?? '').toString(),
|
||||||
|
businessSubtype: (json['business_subtype'] ?? '').toString(),
|
||||||
|
msg: (json['msg'] ?? '').toString(),
|
||||||
|
status: (json['status'] ?? 'pending').toString(),
|
||||||
|
actions: ((json['actions'] as List?) ?? [])
|
||||||
|
.map((item) => ConfirmAction.fromJson(Map<String, dynamic>.from(item as Map)))
|
||||||
|
.toList(),
|
||||||
|
createdAtMs: (json['created_at_ms'] as num?)?.toInt() ?? 0,
|
||||||
|
expireAtMs: (json['expire_at_ms'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfirmAction {
|
||||||
|
final String action;
|
||||||
|
final String label;
|
||||||
|
final String style;
|
||||||
|
|
||||||
|
ConfirmAction({
|
||||||
|
required this.action,
|
||||||
|
required this.label,
|
||||||
|
required this.style,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ConfirmAction.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ConfirmAction(
|
||||||
|
action: (json['action'] ?? '').toString(),
|
||||||
|
label: (json['label'] ?? '').toString(),
|
||||||
|
style: (json['style'] ?? '').toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfirmStatus {
|
||||||
|
final String confirmId;
|
||||||
|
final String status;
|
||||||
|
final String processedAction;
|
||||||
|
final int processedAtMs;
|
||||||
|
|
||||||
|
ConfirmStatus({
|
||||||
|
required this.confirmId,
|
||||||
|
required this.status,
|
||||||
|
required this.processedAction,
|
||||||
|
required this.processedAtMs,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool get canOperate => status == 'pending';
|
||||||
|
|
||||||
|
factory ConfirmStatus.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ConfirmStatus(
|
||||||
|
confirmId: (json['confirm_id'] ?? '').toString(),
|
||||||
|
status: (json['status'] ?? '').toString(),
|
||||||
|
processedAction: (json['processed_action'] ?? '').toString(),
|
||||||
|
processedAtMs: (json['processed_at_ms'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## IM 解析
|
||||||
|
|
||||||
|
```dart
|
||||||
|
ImConfirmMessage? parseImConfirmCustomData(String data) {
|
||||||
|
final dynamic decoded = jsonDecode(data);
|
||||||
|
if (decoded is! Map) return null;
|
||||||
|
final map = Map<String, dynamic>.from(decoded);
|
||||||
|
if (map['type'] != 'im_confirm') return null;
|
||||||
|
final confirmId = (map['confirm_id'] ?? '').toString();
|
||||||
|
if (confirmId.isEmpty) return null;
|
||||||
|
return ImConfirmMessage.fromJson(map);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
解析规则:
|
||||||
|
|
||||||
|
- 只处理 `type == im_confirm`。
|
||||||
|
- `confirm_id` 为空时按普通不支持消息处理。
|
||||||
|
- `msg` 直接展示,不在 Flutter 端拼接业务文案。
|
||||||
|
- `actions` 为空时只展示文案,不展示按钮。
|
||||||
|
|
||||||
|
## API 调用
|
||||||
|
|
||||||
|
```dart
|
||||||
|
String newConfirmCommandId(String confirmId, String action) {
|
||||||
|
return '${confirmId}_${action}_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ConfirmStatus> acceptConfirm(String confirmId) async {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('$baseUrl/api/v1/message/action-confirms/$confirmId/accept'),
|
||||||
|
headers: authJsonHeaders(),
|
||||||
|
body: jsonEncode({
|
||||||
|
'command_id': newConfirmCommandId(confirmId, 'accept'),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return parseConfirmStatusEnvelope(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ConfirmStatus> rejectConfirm(String confirmId, {String reason = ''}) async {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('$baseUrl/api/v1/message/action-confirms/$confirmId/reject'),
|
||||||
|
headers: authJsonHeaders(),
|
||||||
|
body: jsonEncode({
|
||||||
|
'command_id': newConfirmCommandId(confirmId, 'reject'),
|
||||||
|
'reason': reason,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return parseConfirmStatusEnvelope(response);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`command_id` 每次点击生成一个新值。网络重试同一次请求时可以复用同一个 `command_id`。
|
||||||
|
|
||||||
|
## 状态刷新
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Future<Map<String, ConfirmStatus>> batchRefreshConfirmStatus(List<String> confirmIds) async {
|
||||||
|
final ids = confirmIds.where((id) => id.isNotEmpty).toSet().toList();
|
||||||
|
if (ids.isEmpty) return {};
|
||||||
|
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('$baseUrl/api/v1/message/action-confirms/status'),
|
||||||
|
headers: authJsonHeaders(),
|
||||||
|
body: jsonEncode({'confirm_ids': ids}),
|
||||||
|
);
|
||||||
|
final data = parseEnvelopeData(response);
|
||||||
|
final items = (data['items'] as List? ?? []);
|
||||||
|
return {
|
||||||
|
for (final item in items)
|
||||||
|
ConfirmStatus.fromJson(Map<String, dynamic>.from(item as Map)).confirmId:
|
||||||
|
ConfirmStatus.fromJson(Map<String, dynamic>.from(item as Map)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
刷新时机:
|
||||||
|
|
||||||
|
- 进入 IM 会话页。
|
||||||
|
- App resume。
|
||||||
|
- 收到新的 `im_confirm`。
|
||||||
|
- 接受或拒绝接口返回 `CONFLICT`。
|
||||||
|
|
||||||
|
## UI 规则
|
||||||
|
|
||||||
|
- 气泡文案展示 `msg`。
|
||||||
|
- `status == pending` 时展示 `actions` 里的按钮。
|
||||||
|
- `status != pending` 时按钮置灰,不能再次点击。
|
||||||
|
- 本地发起请求后,当前按钮进入 loading,同一条消息两个按钮都置灰。
|
||||||
|
- 接口成功后,用返回状态覆盖本地状态。
|
||||||
|
- 接口失败但不是鉴权错误时,调用状态接口刷新一次。
|
||||||
|
|
||||||
|
按钮样式建议:
|
||||||
|
|
||||||
|
| style | 展示 |
|
||||||
|
| --- | --- |
|
||||||
|
| `primary` | 主按钮,例如绿色 Accept |
|
||||||
|
| `secondary` | 次按钮,例如描边 Reject |
|
||||||
|
|
||||||
|
Flutter 不要根据 `business_subtype` 写死按钮文案,按钮文案以 `actions.label` 为准。
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
| code | Flutter 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| `UNAUTHORIZED` | 重新登录 |
|
||||||
|
| `PERMISSION_DENIED` | 置灰并提示无权限 |
|
||||||
|
| `NOT_FOUND` | 置灰,消息已失效 |
|
||||||
|
| `CONFLICT` | 调状态接口刷新,按最新状态置灰 |
|
||||||
|
| `UPSTREAM_ERROR` | 解除 loading,允许稍后重试 |
|
||||||
|
| `INTERNAL_ERROR` | 解除 loading,提示稍后重试 |
|
||||||
|
|
||||||
|
重复点击、跨设备已处理、旧消息重新打开,都以状态接口返回为准。
|
||||||
|
|
||||||
|
## 对接检查
|
||||||
|
|
||||||
|
- 收到 `im_confirm` 后能渲染文案和两个按钮。
|
||||||
|
- 点击 Accept 后调用接受接口,成功后按钮置灰。
|
||||||
|
- 点击 Reject 后调用拒绝接口,成功后按钮置灰。
|
||||||
|
- 同一条消息重复点击不会重复请求;如果请求已经发出,两个按钮都置灰。
|
||||||
|
- App 重新进入会话后,通过状态接口刷新旧消息状态。
|
||||||
|
- 另一个设备处理后,本设备 resume 能刷新为终态。
|
||||||
|
- `CONFLICT` 时不弹业务成功,只刷新状态后展示最终状态。
|
||||||
422
docs/通用确认消息方案.md
Normal file
422
docs/通用确认消息方案.md
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
# 通用确认消息方案
|
||||||
|
|
||||||
|
本文定义 App 私聊中可操作确认消息的后端方案。首个业务场景是 `host / agency / bd` 身份邀请,后续其他业务只新增 `business_type` 处理器,不改变 Flutter 的通用消息渲染和确认接口。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 腾讯云 IM 私聊里支持通用确认消息,App 可以展示文案和 `Accept / Reject` 等操作按钮。
|
||||||
|
- App 点击按钮后只提交 `confirm_id` 和动作,不提交业务参数。
|
||||||
|
- 后端根据 `confirm_id` 查出业务类型、业务主键、发送人、接收人和当前状态,再调用对应 owner service 执行业务。
|
||||||
|
- 处理成功后确认消息进入终态,App 再次渲染同一条 IM 时按钮置灰。
|
||||||
|
- 当前用户只能处理发给自己的确认消息,不能替别人确认,也不能枚举别人的消息状态。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不用 `confirm` 表替代业务事实表。`role_invitations.status` 仍然是身份邀请的权威状态。
|
||||||
|
- 不让客户端传 `business_type`、`business_id`、`sender_user_id` 或 `receiver_user_id` 来决定业务行为。
|
||||||
|
- 不把确认消息做成房间状态、连接态或腾讯云 IM 会话状态的 owner。
|
||||||
|
- 本方案不包含 `bd_leader` 邀请,当前只覆盖 `host / agency / bd`。
|
||||||
|
|
||||||
|
## 服务边界
|
||||||
|
|
||||||
|
| 模块 | 职责 |
|
||||||
|
| --- | --- |
|
||||||
|
| `gateway-service` | 提供 `/api/v1` HTTP 入口,透传当前登录用户和 `app_code`,不直接写确认表 |
|
||||||
|
| `message action module` | 确认消息 owner,首版落在 `activity-service/internal/service/message`;负责确认表、状态机、幂等和业务分发 |
|
||||||
|
| `user-service` | `host / agency / bd` 业务 owner,负责 `role_invitations` 和 `ProcessRoleInvitation` |
|
||||||
|
| `notice-service` | 只负责把确认消息投递到腾讯云 IM C2C,不执行确认业务 |
|
||||||
|
| 腾讯云 IM | 私聊消息、离线、漫游和客户端长连接 |
|
||||||
|
|
||||||
|
首版物理落点建议:
|
||||||
|
|
||||||
|
- 确认表放在 `hyapp_activity`,由 `activity-service` 的 message 模块管理。
|
||||||
|
- `gateway-service` 新增 action confirm HTTP handler,内部调用 activity-service gRPC。
|
||||||
|
- `notice-service` 消费 message action outbox,发送腾讯 IM C2C 自定义消息。
|
||||||
|
|
||||||
|
## 总流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as Sender App
|
||||||
|
participant Gateway as gateway-service
|
||||||
|
participant User as user-service
|
||||||
|
participant UserDB as hyapp_user
|
||||||
|
participant Message as activity-service message module
|
||||||
|
participant MsgDB as hyapp_activity
|
||||||
|
participant Notice as notice-service
|
||||||
|
participant TIM as Tencent IM
|
||||||
|
participant B as Receiver App
|
||||||
|
|
||||||
|
A->>Gateway: 创建 host/agency/bd 邀请
|
||||||
|
Gateway->>User: InviteHost / InviteAgency / InviteBD
|
||||||
|
User->>UserDB: tx 写 role_invitations + user_outbox
|
||||||
|
User-->>Gateway: invitation
|
||||||
|
Gateway-->>A: invitation
|
||||||
|
Message->>UserDB: consume RoleInvitationCreated
|
||||||
|
Message->>MsgDB: 创建 message_action_confirms + message_action_outbox
|
||||||
|
Notice->>MsgDB: consume ConfirmMessageCreated
|
||||||
|
Notice->>TIM: C2C TIMCustomElem(type=im_confirm)
|
||||||
|
TIM-->>B: IM 确认消息
|
||||||
|
B->>Gateway: POST /api/v1/message/action-confirms/{confirm_id}/accept
|
||||||
|
Gateway->>Message: AcceptActionConfirm
|
||||||
|
Message->>MsgDB: pending -> processing
|
||||||
|
Message->>User: ProcessRoleInvitation(action=accept)
|
||||||
|
User->>UserDB: tx 推进 role_invitations 终态
|
||||||
|
Message->>MsgDB: processing -> accepted
|
||||||
|
Gateway-->>B: confirm status accepted
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据表
|
||||||
|
|
||||||
|
### message_action_confirms
|
||||||
|
|
||||||
|
确认消息的当前状态表。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE message_action_confirms (
|
||||||
|
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
|
||||||
|
confirm_id VARCHAR(96) NOT NULL,
|
||||||
|
business_type VARCHAR(64) NOT NULL,
|
||||||
|
business_subtype VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
business_id VARCHAR(96) NOT NULL,
|
||||||
|
sender_user_id BIGINT NOT NULL,
|
||||||
|
receiver_user_id BIGINT NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
message_text VARCHAR(1024) NOT NULL,
|
||||||
|
actions_json JSON NOT NULL,
|
||||||
|
payload_json JSON NULL,
|
||||||
|
source_name VARCHAR(64) NOT NULL,
|
||||||
|
source_event_id VARCHAR(128) NOT NULL,
|
||||||
|
processing_action VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
processing_command_id VARCHAR(96) NOT NULL DEFAULT '',
|
||||||
|
locked_by VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
lock_until_ms BIGINT NOT NULL DEFAULT 0,
|
||||||
|
processed_action VARCHAR(32) NOT NULL DEFAULT '',
|
||||||
|
processed_command_id VARCHAR(96) NOT NULL DEFAULT '',
|
||||||
|
processed_by_user_id BIGINT NULL,
|
||||||
|
processed_at_ms BIGINT NULL,
|
||||||
|
expire_at_ms BIGINT NULL,
|
||||||
|
created_at_ms BIGINT NOT NULL,
|
||||||
|
updated_at_ms BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (app_code, confirm_id),
|
||||||
|
UNIQUE KEY uk_confirm_source_event (app_code, source_name, source_event_id),
|
||||||
|
KEY idx_confirm_receiver_status (app_code, receiver_user_id, status, created_at_ms),
|
||||||
|
KEY idx_confirm_peer (app_code, receiver_user_id, sender_user_id, created_at_ms),
|
||||||
|
KEY idx_confirm_business (app_code, business_type, business_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
字段规则:
|
||||||
|
|
||||||
|
- `confirm_id` 由服务端生成,例如 `cfm_xxx`。
|
||||||
|
- `business_type` 首版支持 `role_invitation`。
|
||||||
|
- `business_subtype` 首版支持 `host`、`agency`、`bd`。
|
||||||
|
- `business_id` 对 `role_invitation` 来说就是 `role_invitations.invitation_id`。
|
||||||
|
- `sender_user_id` 是邀请发起人,`receiver_user_id` 是目标用户。
|
||||||
|
- `message_text` 是 IM 展示快照,业务判断不能依赖它。
|
||||||
|
- `actions_json` 首版为 `["accept","reject"]`。
|
||||||
|
- `payload_json` 只放展示扩展,例如邀请人昵称、公会名、头像等。
|
||||||
|
- `source_name + source_event_id` 用于从 owner outbox 重试时幂等创建确认消息。
|
||||||
|
|
||||||
|
### message_action_outbox
|
||||||
|
|
||||||
|
确认消息投递事件表,由 `notice-service` 消费后发腾讯 IM。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE message_action_outbox (
|
||||||
|
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu',
|
||||||
|
event_id VARCHAR(128) NOT NULL,
|
||||||
|
event_type VARCHAR(64) NOT NULL,
|
||||||
|
confirm_id VARCHAR(96) NOT NULL,
|
||||||
|
receiver_user_id BIGINT NOT NULL,
|
||||||
|
status VARCHAR(32) NOT NULL,
|
||||||
|
payload_json JSON NOT NULL,
|
||||||
|
created_at_ms BIGINT NOT NULL,
|
||||||
|
retry_count INT NOT NULL DEFAULT 0,
|
||||||
|
last_error VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (app_code, event_id),
|
||||||
|
KEY idx_message_action_outbox_status (app_code, status, created_at_ms)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
首版事件:
|
||||||
|
|
||||||
|
- `ConfirmMessageCreated`:发送一条新的 `im_confirm`。
|
||||||
|
- 后续如果需要跨设备实时置灰,可新增 `ConfirmMessageProcessed`,投递状态变更 IM。首版可以通过状态接口刷新。
|
||||||
|
|
||||||
|
## 状态机
|
||||||
|
|
||||||
|
| 状态 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `pending` | 待处理,App 展示可点击按钮 |
|
||||||
|
| `processing` | 后端正在执行,App 按钮置灰并显示处理中 |
|
||||||
|
| `accepted` | 已接受,终态 |
|
||||||
|
| `rejected` | 已拒绝,终态 |
|
||||||
|
| `cancelled` | 业务方取消,终态 |
|
||||||
|
| `expired` | 超时未处理,终态 |
|
||||||
|
| `failed` | 后端处理失败且需要人工排查,终态 |
|
||||||
|
|
||||||
|
状态推进规则:
|
||||||
|
|
||||||
|
- 只有 `receiver_user_id == 当前登录用户` 才能接受或拒绝。
|
||||||
|
- `pending` 才能进入 `processing`。
|
||||||
|
- `processing` 通过 `locked_by + lock_until_ms` 防止重复点击并发执行。
|
||||||
|
- owner service 成功后,`processing` 进入 `accepted` 或 `rejected`。
|
||||||
|
- 如果服务在 owner service 成功后、confirm 状态更新前崩溃,下一次同 `confirm_id` 请求必须用 `business_type + business_id` 查询 owner 当前事实,修复 confirm 状态。
|
||||||
|
|
||||||
|
## 业务类型
|
||||||
|
|
||||||
|
### role_invitation
|
||||||
|
|
||||||
|
首版处理 `host / agency / bd`。
|
||||||
|
|
||||||
|
| subtype | business_id | accept 动作 | reject 动作 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `host` | `role_invitations.invitation_id` | 调 user-service `ProcessRoleInvitation(action=accept)` | 调 user-service `ProcessRoleInvitation(action=reject)` |
|
||||||
|
| `agency` | `role_invitations.invitation_id` | 调 user-service `ProcessRoleInvitation(action=accept)` | 调 user-service `ProcessRoleInvitation(action=reject)` |
|
||||||
|
| `bd` | `role_invitations.invitation_id` | 调 user-service `ProcessRoleInvitation(action=accept)` | 调 user-service `ProcessRoleInvitation(action=reject)` |
|
||||||
|
|
||||||
|
`role_invitation` 的最终状态以 user-service 为准。confirm 模块只保存按钮状态和业务路由。
|
||||||
|
|
||||||
|
## HTTP 接口
|
||||||
|
|
||||||
|
所有接口都走 `/api/v1` envelope。
|
||||||
|
|
||||||
|
### 接受确认消息
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/{confirm_id}/accept
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command_id": "cfm_123_accept_001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "accepted",
|
||||||
|
"processed_action": "accept",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:收到 `type=im_confirm` 且 `confirm_id=cfm_123` 的消息后,点击 `accept` 调此接口。
|
||||||
|
|
||||||
|
### 拒绝确认消息
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/{confirm_id}/reject
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"command_id": "cfm_123_reject_001",
|
||||||
|
"reason": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "rejected",
|
||||||
|
"processed_action": "reject",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:收到 `type=im_confirm` 且 `confirm_id=cfm_123` 的消息后,点击 `reject` 调此接口。
|
||||||
|
|
||||||
|
### 批量查询确认状态
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /api/v1/message/action-confirms/status
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"confirm_ids": ["cfm_123", "cfm_456"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "accepted",
|
||||||
|
"processed_action": "accept",
|
||||||
|
"processed_at_ms": 1710000001000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:App 打开会话或回到前台时,收集当前页面内的 `confirm_id` 后调用该接口刷新按钮状态。
|
||||||
|
|
||||||
|
### 按会话查询确认状态
|
||||||
|
|
||||||
|
地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/v1/message/action-confirms?peer_user_id=10001&page_size=50
|
||||||
|
```
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
| 参数 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `peer_user_id` | 是 | 对方用户 ID;当前用户从 token 取,不能由客户端传 |
|
||||||
|
| `page_size` | 否 | 默认 50,最大 100 |
|
||||||
|
| `page_token` | 否 | 分页游标 |
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"sender_user_id": "10001",
|
||||||
|
"receiver_user_id": "10002",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"business_id": "987",
|
||||||
|
"status": "pending",
|
||||||
|
"processed_action": "",
|
||||||
|
"processed_at_ms": 0,
|
||||||
|
"created_at_ms": 1710000000000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_page_token": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相关 IM:App 如果无法从本地消息列表快速收集 `confirm_id`,可以按当前会话刷新最近确认消息状态。
|
||||||
|
|
||||||
|
## IM 负载
|
||||||
|
|
||||||
|
腾讯 IM 使用 C2C `TIMCustomElem`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "im_confirm",
|
||||||
|
"version": 1,
|
||||||
|
"confirm_id": "cfm_123",
|
||||||
|
"business_type": "role_invitation",
|
||||||
|
"business_subtype": "host",
|
||||||
|
"msg": "Alice has invited you to join ABC guild",
|
||||||
|
"status": "pending",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "reject",
|
||||||
|
"label": "Reject",
|
||||||
|
"style": "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"label": "Accept",
|
||||||
|
"style": "primary"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at_ms": 1710000000000,
|
||||||
|
"expire_at_ms": 1710604800000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
腾讯 IM 字段建议:
|
||||||
|
|
||||||
|
| TIM 字段 | 值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `Desc` | `im_confirm` |
|
||||||
|
| `Ext` | `im_confirm` |
|
||||||
|
| `Data` | 上面的 JSON 字符串 |
|
||||||
|
|
||||||
|
App 只依赖 `type`、`confirm_id`、`msg`、`status` 和 `actions`。`business_type` 只用于日志和埋点,不用于 App 决定业务接口。
|
||||||
|
|
||||||
|
## 创建消息规则
|
||||||
|
|
||||||
|
`host / agency / bd` 邀请创建成功后,user-service 写 `RoleInvitationCreated` outbox。payload 必须包含:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event_type": "RoleInvitationCreated",
|
||||||
|
"invitation_id": "987",
|
||||||
|
"invitation_type": "host",
|
||||||
|
"status": "pending",
|
||||||
|
"inviter_user_id": "10001",
|
||||||
|
"target_user_id": "10002",
|
||||||
|
"agency_name": "ABC guild",
|
||||||
|
"created_at_ms": 1710000000000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
message action worker 根据该 payload 创建:
|
||||||
|
|
||||||
|
- `business_type = role_invitation`
|
||||||
|
- `business_subtype = invitation_type`
|
||||||
|
- `business_id = invitation_id`
|
||||||
|
- `sender_user_id = inviter_user_id`
|
||||||
|
- `receiver_user_id = target_user_id`
|
||||||
|
- `actions_json = ["accept","reject"]`
|
||||||
|
|
||||||
|
## 错误码
|
||||||
|
|
||||||
|
| HTTP | code | 场景 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 400 | `INVALID_ARGUMENT` | `confirm_id`、`command_id` 或 action 参数非法 |
|
||||||
|
| 401 | `UNAUTHORIZED` | 未登录或 token 无效 |
|
||||||
|
| 403 | `PERMISSION_DENIED` | 当前用户不是接收人 |
|
||||||
|
| 404 | `NOT_FOUND` | `confirm_id` 不存在 |
|
||||||
|
| 409 | `CONFLICT` | 已处理、已过期、处理中或业务状态不允许操作 |
|
||||||
|
| 502 | `UPSTREAM_ERROR` | owner service 不可用 |
|
||||||
|
|
||||||
|
## 开发顺序
|
||||||
|
|
||||||
|
1. 在 activity-service message 模块新增 `message_action_confirms` 和 `message_action_outbox`。
|
||||||
|
2. 新增 `ActionConfirmService` gRPC:`AcceptActionConfirm`、`RejectActionConfirm`、`BatchGetActionConfirmStatus`、`ListConversationActionConfirms`。
|
||||||
|
3. gateway 新增 HTTP 接口并调用 action confirm gRPC。
|
||||||
|
4. user-service 邀请创建事务写 `RoleInvitationCreated` outbox。
|
||||||
|
5. activity-service 增加 user outbox consumer,创建 confirm row 和 message action outbox。
|
||||||
|
6. notice-service 增加 message action outbox consumer,发送 `im_confirm`。
|
||||||
|
7. 补 `role_invitation` business handler,接受/拒绝时调用 user-service `ProcessRoleInvitation`。
|
||||||
|
8. 补状态修复逻辑:confirm 仍 pending/processing 但 user-service 邀请已经终态时,以 user-service 状态回写 confirm。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 创建 Host 邀请后,能查到 `role_invitations.pending`、`message_action_confirms.pending` 和一条待投递 outbox。
|
||||||
|
- notice-service 投递后,App 收到 `type=im_confirm`。
|
||||||
|
- 接收人接受后,`role_invitations.status=accepted`,confirm 状态为 `accepted`。
|
||||||
|
- 接收人拒绝后,`role_invitations.status=rejected`,confirm 状态为 `rejected`。
|
||||||
|
- 非接收人调用接受/拒绝接口返回 `PERMISSION_DENIED`。
|
||||||
|
- 重复点击同一个动作返回同一个终态,不重复创建业务事实。
|
||||||
|
- 已接受后再拒绝返回 `CONFLICT`,状态接口返回 `accepted`。
|
||||||
63
pkg/messagemq/messages.go
Normal file
63
pkg/messagemq/messages.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Package messagemq defines RocketMQ payloads owned by activity-service message modules.
|
||||||
|
package messagemq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MessageTypeActionOutboxEvent = "message_action_outbox_event"
|
||||||
|
|
||||||
|
TagMessageActionOutboxEvent = "message_action_outbox_event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionOutboxMessage is the MQ representation of one committed message_action_outbox fact.
|
||||||
|
type ActionOutboxMessage struct {
|
||||||
|
MessageType string `json:"message_type"`
|
||||||
|
AppCode string `json:"app_code"`
|
||||||
|
EventID string `json:"event_id"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
ConfirmID string `json:"confirm_id"`
|
||||||
|
PayloadJSON string `json:"payload_json"`
|
||||||
|
OccurredAtMS int64 `json:"occurred_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeActionOutboxMessage serializes a confirm-message fact for notice-service delivery.
|
||||||
|
func EncodeActionOutboxMessage(message ActionOutboxMessage) ([]byte, error) {
|
||||||
|
message.MessageType = MessageTypeActionOutboxEvent
|
||||||
|
if strings.TrimSpace(message.AppCode) == "" ||
|
||||||
|
strings.TrimSpace(message.EventID) == "" ||
|
||||||
|
strings.TrimSpace(message.EventType) == "" ||
|
||||||
|
strings.TrimSpace(message.ConfirmID) == "" ||
|
||||||
|
message.OccurredAtMS <= 0 {
|
||||||
|
return nil, errors.New("message action outbox message is incomplete")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(message.PayloadJSON) == "" {
|
||||||
|
message.PayloadJSON = "{}"
|
||||||
|
}
|
||||||
|
return json.Marshal(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeActionOutboxMessage validates one confirm-message MQ body.
|
||||||
|
func DecodeActionOutboxMessage(body []byte) (ActionOutboxMessage, error) {
|
||||||
|
var message ActionOutboxMessage
|
||||||
|
if err := json.Unmarshal(body, &message); err != nil {
|
||||||
|
return ActionOutboxMessage{}, err
|
||||||
|
}
|
||||||
|
if message.MessageType != MessageTypeActionOutboxEvent {
|
||||||
|
return ActionOutboxMessage{}, errors.New("unexpected message action outbox message_type")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(message.AppCode) == "" ||
|
||||||
|
strings.TrimSpace(message.EventID) == "" ||
|
||||||
|
strings.TrimSpace(message.EventType) == "" ||
|
||||||
|
strings.TrimSpace(message.ConfirmID) == "" ||
|
||||||
|
message.OccurredAtMS <= 0 {
|
||||||
|
return ActionOutboxMessage{}, errors.New("message action outbox message is incomplete")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(message.PayloadJSON) == "" {
|
||||||
|
message.PayloadJSON = "{}"
|
||||||
|
}
|
||||||
|
return message, nil
|
||||||
|
}
|
||||||
@ -62,12 +62,12 @@ var catalog = map[Code]Spec{
|
|||||||
RegionNotFound: spec(codes.NotFound, httpStatusNotFound, RegionNotFound, "not found"),
|
RegionNotFound: spec(codes.NotFound, httpStatusNotFound, RegionNotFound, "not found"),
|
||||||
RegionCountryConflict: spec(codes.FailedPrecondition, httpStatusConflict, RegionCountryConflict, "conflict"),
|
RegionCountryConflict: spec(codes.FailedPrecondition, httpStatusConflict, RegionCountryConflict, "conflict"),
|
||||||
RegionDisabled: spec(codes.FailedPrecondition, httpStatusConflict, RegionDisabled, "conflict"),
|
RegionDisabled: spec(codes.FailedPrecondition, httpStatusConflict, RegionDisabled, "conflict"),
|
||||||
InvalidInviteCode: spec(codes.InvalidArgument, httpStatusBadRequest, InvalidInviteCode, "invalid argument"),
|
InvalidInviteCode: spec(codes.InvalidArgument, httpStatusBadRequest, InvalidInviteCode, "invalid invite code"),
|
||||||
DeviceAlreadyRegistered: spec(
|
DeviceAlreadyRegistered: spec(
|
||||||
codes.FailedPrecondition,
|
codes.FailedPrecondition,
|
||||||
httpStatusConflict,
|
httpStatusConflict,
|
||||||
DeviceAlreadyRegistered,
|
DeviceAlreadyRegistered,
|
||||||
"conflict",
|
"device already registered",
|
||||||
),
|
),
|
||||||
|
|
||||||
InsufficientBalance: spec(codes.FailedPrecondition, httpStatusConflict, InsufficientBalance, "insufficient balance"),
|
InsufficientBalance: spec(codes.FailedPrecondition, httpStatusConflict, InsufficientBalance, "insufficient balance"),
|
||||||
|
|||||||
@ -60,7 +60,7 @@ func TestCatalogMappings(t *testing.T) {
|
|||||||
grpcCode: codes.InvalidArgument,
|
grpcCode: codes.InvalidArgument,
|
||||||
httpStatus: httpStatusBadRequest,
|
httpStatus: httpStatusBadRequest,
|
||||||
publicCode: string(InvalidInviteCode),
|
publicCode: string(InvalidInviteCode),
|
||||||
publicMessage: "invalid argument",
|
publicMessage: "invalid invite code",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unavailable is exposed as upstream error",
|
name: "unavailable is exposed as upstream error",
|
||||||
@ -86,6 +86,14 @@ func TestCatalogMappings(t *testing.T) {
|
|||||||
publicCode: string(CountryChangeCooldown),
|
publicCode: string(CountryChangeCooldown),
|
||||||
publicMessage: "country change is cooling down",
|
publicMessage: "country change is cooling down",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "registered device conflict exposes concrete login reason",
|
||||||
|
code: DeviceAlreadyRegistered,
|
||||||
|
grpcCode: codes.FailedPrecondition,
|
||||||
|
httpStatus: httpStatusConflict,
|
||||||
|
publicCode: string(DeviceAlreadyRegistered),
|
||||||
|
publicMessage: "device already registered",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ TOPICS=(
|
|||||||
"hyapp_robot_room_outbox"
|
"hyapp_robot_room_outbox"
|
||||||
"hyapp_room_rocket_launch"
|
"hyapp_room_rocket_launch"
|
||||||
"hyapp_user_outbox"
|
"hyapp_user_outbox"
|
||||||
|
"hyapp_message_action_outbox"
|
||||||
"hyapp_game_outbox"
|
"hyapp_game_outbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,13 @@ CREATE TABLE IF NOT EXISTS manager_profiles (
|
|||||||
contact_info VARCHAR(128) NOT NULL DEFAULT '' COMMENT '联系信息',
|
contact_info VARCHAR(128) NOT NULL DEFAULT '' COMMENT '联系信息',
|
||||||
can_grant_avatar_frame TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送头像框',
|
can_grant_avatar_frame TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送头像框',
|
||||||
can_grant_vehicle TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送座驾',
|
can_grant_vehicle TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送座驾',
|
||||||
|
can_grant_badge TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送徽章',
|
||||||
can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心升级用户等级',
|
can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心升级用户等级',
|
||||||
can_add_bd_leader TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 BD Leader',
|
can_add_bd_leader TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 BD Leader',
|
||||||
can_add_admin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Admin',
|
can_add_admin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Admin',
|
||||||
can_add_superadmin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Superadmin',
|
can_add_superadmin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Superadmin',
|
||||||
can_block_user TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心封禁用户',
|
can_block_user TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心封禁用户',
|
||||||
|
can_transfer_user_country TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心转移用户国家',
|
||||||
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建后台管理员 ID',
|
created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建后台管理员 ID',
|
||||||
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||||
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||||
@ -48,9 +50,18 @@ PREPARE stmt FROM @ddl;
|
|||||||
EXECUTE stmt;
|
EXECUTE stmt;
|
||||||
DEALLOCATE PREPARE stmt;
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl := IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_grant_badge') = 0,
|
||||||
|
'ALTER TABLE manager_profiles ADD COLUMN can_grant_badge TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否允许在经理中心赠送徽章'' AFTER can_grant_vehicle',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
SET @ddl := IF(
|
SET @ddl := IF(
|
||||||
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_update_user_level') = 0,
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_update_user_level') = 0,
|
||||||
'ALTER TABLE manager_profiles ADD COLUMN can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否允许在经理中心升级用户等级'' AFTER can_grant_vehicle',
|
'ALTER TABLE manager_profiles ADD COLUMN can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否允许在经理中心升级用户等级'' AFTER can_grant_badge',
|
||||||
'SELECT 1'
|
'SELECT 1'
|
||||||
);
|
);
|
||||||
PREPARE stmt FROM @ddl;
|
PREPARE stmt FROM @ddl;
|
||||||
@ -93,9 +104,18 @@ PREPARE stmt FROM @ddl;
|
|||||||
EXECUTE stmt;
|
EXECUTE stmt;
|
||||||
DEALLOCATE PREPARE stmt;
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl := IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'manager_profiles' AND COLUMN_NAME = 'can_transfer_user_country') = 0,
|
||||||
|
'ALTER TABLE manager_profiles ADD COLUMN can_transfer_user_country TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''是否允许在经理中心转移用户国家'' AFTER can_block_user',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
INSERT INTO manager_profiles (
|
INSERT INTO manager_profiles (
|
||||||
app_code, user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle,
|
app_code, user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle, can_grant_badge,
|
||||||
can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user,
|
can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country,
|
||||||
created_by_admin_id, created_at_ms, updated_at_ms
|
created_by_admin_id, created_at_ms, updated_at_ms
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
@ -110,6 +130,8 @@ SELECT
|
|||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
0,
|
0,
|
||||||
MIN(bl.created_at_ms),
|
MIN(bl.created_at_ms),
|
||||||
MAX(bl.updated_at_ms)
|
MAX(bl.updated_at_ms)
|
||||||
|
|||||||
861
scripts/ops/refresh_robot_appearance.py
Executable file
861
scripts/ops/refresh_robot_appearance.py
Executable file
@ -0,0 +1,861 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Refresh robot avatar frames and VIP badges.
|
||||||
|
|
||||||
|
The script is intentionally dry-run by default. With --yes it:
|
||||||
|
1. Builds the robot user set from users.source=game_robot plus robot-service pools.
|
||||||
|
2. Picks avatar frames priced at or below 2,000,000 COIN, excluding known reserved frames.
|
||||||
|
3. Picks one VIP strip badge and one VIP tile badge.
|
||||||
|
4. Ensures wallet entitlements exist, replaces equipped avatar/badge rows, and rebuilds activity badge display slots.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
APP_CODE_DEFAULT = "lalu"
|
||||||
|
ROBOT_SOURCE_DEFAULT = "game_robot"
|
||||||
|
GRANT_SOURCE = "game_robot_init"
|
||||||
|
RESOURCE_OUTBOX_ASSET = "RESOURCE"
|
||||||
|
OUTBOX_STATUS_PENDING = "pending"
|
||||||
|
ENTITLEMENT_STATUS_ACTIVE = "active"
|
||||||
|
GRANT_STATUS_DONE = "succeeded"
|
||||||
|
RESULT_ENTITLEMENT = "entitlement"
|
||||||
|
ROBOT_RESOURCE_DURATION_DAYS = 9999
|
||||||
|
ROBOT_RESOURCE_DURATION_MS = ROBOT_RESOURCE_DURATION_DAYS * 24 * 60 * 60 * 1000
|
||||||
|
ROBOT_AVATAR_FRAME_MAX_COIN_PRICE = 2_000_000
|
||||||
|
EXCLUDED_AVATAR_FRAME_IDS = {165, 175}
|
||||||
|
BADGE_DISPLAY_SLOTS = ("profile_strip", "profile_tile", "honor_wall")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_app_code(value: str) -> str:
|
||||||
|
return (value or "").strip().lower() or APP_CODE_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
|
def stable_hash(value: str) -> str:
|
||||||
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def prefixed_id(prefix: str, value: str) -> str:
|
||||||
|
return prefix + stable_hash(value)
|
||||||
|
|
||||||
|
|
||||||
|
def current_ms() -> int:
|
||||||
|
return int(time.time() * 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def read_config_text(location: str) -> str:
|
||||||
|
location = location.strip()
|
||||||
|
if looks_like_ssh_location(location):
|
||||||
|
host, remote_path = location.split(":", 1)
|
||||||
|
return subprocess.check_output(
|
||||||
|
["ssh", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=5", host, "cat " + shlex.quote(remote_path)],
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return Path(location).expanduser().read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def looks_like_ssh_location(location: str) -> bool:
|
||||||
|
if location.startswith("/") or location.startswith("./") or location.startswith("../") or "://" in location:
|
||||||
|
return False
|
||||||
|
head, sep, tail = location.partition(":")
|
||||||
|
return bool(sep and tail and ("@" in head or "." in head or head.replace("-", "").isalnum()))
|
||||||
|
|
||||||
|
|
||||||
|
def load_yaml(location: str) -> dict[str, Any]:
|
||||||
|
payload = yaml.safe_load(read_config_text(location)) or {}
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise RuntimeError(f"config must be a YAML object: {location}")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def parse_go_mysql_dsn(dsn: str) -> dict[str, Any]:
|
||||||
|
prefix, rest = dsn.split("@tcp(", 1)
|
||||||
|
user, password = prefix.split(":", 1) if ":" in prefix else (prefix, "")
|
||||||
|
addr, rest = rest.split(")/", 1)
|
||||||
|
host, port = addr.rsplit(":", 1) if ":" in addr else (addr, "3306")
|
||||||
|
database = rest.split("?", 1)[0]
|
||||||
|
return {
|
||||||
|
"host": host,
|
||||||
|
"port": int(port),
|
||||||
|
"user": user,
|
||||||
|
"password": password,
|
||||||
|
"database": database,
|
||||||
|
"charset": "utf8mb4",
|
||||||
|
"autocommit": False,
|
||||||
|
"connect_timeout": 8,
|
||||||
|
"read_timeout": 120,
|
||||||
|
"write_timeout": 120,
|
||||||
|
"cursorclass": pymysql.cursors.DictCursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def connect_from_config(path: str, dsn_key: str = "mysql_dsn"):
|
||||||
|
cfg = load_yaml(path)
|
||||||
|
dsn = str(cfg.get(dsn_key) or "").strip()
|
||||||
|
if not dsn:
|
||||||
|
raise RuntimeError(f"{dsn_key} is required in {path}")
|
||||||
|
return pymysql.connect(**parse_go_mysql_dsn(dsn))
|
||||||
|
|
||||||
|
|
||||||
|
def table_exists(conn, table_name: str) -> bool:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT COUNT(*) AS count FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
|
||||||
|
(table_name,),
|
||||||
|
)
|
||||||
|
return int(cursor.fetchone()["count"]) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_user_source_robots(conn, app_code: str, source: str) -> dict[int, dict[str, Any]]:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT user_id, status, COALESCE(source, '') AS source
|
||||||
|
FROM users
|
||||||
|
WHERE app_code = %s AND source = %s
|
||||||
|
ORDER BY user_id ASC
|
||||||
|
""",
|
||||||
|
(app_code, source),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
int(row["user_id"]): {"user_status": row["status"], "sources": {f"users.source:{row['source']}"}}
|
||||||
|
for row in cursor.fetchall()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_robot_pool_users(conn, app_code: str) -> dict[int, set[str]]:
|
||||||
|
result: dict[int, set[str]] = {}
|
||||||
|
for table, label_column in (("robot_game_robots", "game_id"), ("robot_room_robots", "room_scene")):
|
||||||
|
if not table_exists(conn, table):
|
||||||
|
continue
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT user_id, status, {label_column} AS label
|
||||||
|
FROM {table}
|
||||||
|
WHERE app_code = %s
|
||||||
|
ORDER BY user_id ASC
|
||||||
|
""",
|
||||||
|
(app_code,),
|
||||||
|
)
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
user_id = int(row["user_id"])
|
||||||
|
result.setdefault(user_id, set()).add(f"{table}:{row['label']}:{row['status']}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def merge_robot_sources(
|
||||||
|
user_robots: dict[int, dict[str, Any]],
|
||||||
|
pool_robots: dict[int, set[str]],
|
||||||
|
) -> dict[int, dict[str, Any]]:
|
||||||
|
merged = dict(user_robots)
|
||||||
|
for user_id, sources in pool_robots.items():
|
||||||
|
item = merged.setdefault(user_id, {"user_status": "", "sources": set()})
|
||||||
|
item["sources"].update(sources)
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def filter_existing_users(conn, app_code: str, robot_map: dict[int, dict[str, Any]]) -> dict[int, dict[str, Any]]:
|
||||||
|
user_ids = sorted(robot_map)
|
||||||
|
if not user_ids:
|
||||||
|
return {}
|
||||||
|
existing: dict[int, str] = {}
|
||||||
|
for chunk in chunks(user_ids, 500):
|
||||||
|
placeholders = ",".join(["%s"] * len(chunk))
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
f"SELECT user_id, status FROM users WHERE app_code = %s AND user_id IN ({placeholders})",
|
||||||
|
[app_code, *chunk],
|
||||||
|
)
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
existing[int(row["user_id"])] = str(row["status"])
|
||||||
|
filtered: dict[int, dict[str, Any]] = {}
|
||||||
|
for user_id in user_ids:
|
||||||
|
if user_id not in existing:
|
||||||
|
continue
|
||||||
|
item = robot_map[user_id]
|
||||||
|
if not item.get("user_status"):
|
||||||
|
item["user_status"] = existing[user_id]
|
||||||
|
filtered[user_id] = item
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_resources(conn, app_code: str) -> list[dict[str, Any]]:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT app_code, resource_id, resource_code, resource_type, name, status,
|
||||||
|
grantable, manager_grant_enabled, grant_strategy, wallet_asset_type,
|
||||||
|
wallet_asset_amount, price_type, coin_price, gift_point_amount,
|
||||||
|
COALESCE(CAST(usage_scope_json AS CHAR), '[]') AS usage_scope_json,
|
||||||
|
asset_url, preview_url, animation_url,
|
||||||
|
COALESCE(CAST(metadata_json AS CHAR), '{}') AS metadata_json,
|
||||||
|
sort_order, created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms
|
||||||
|
FROM resources
|
||||||
|
WHERE app_code = %s AND status = 'active'
|
||||||
|
ORDER BY sort_order ASC, resource_id ASC
|
||||||
|
""",
|
||||||
|
(app_code,),
|
||||||
|
)
|
||||||
|
return list(cursor.fetchall())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_value(raw: str, fallback: Any) -> Any:
|
||||||
|
try:
|
||||||
|
return json.loads(raw or "")
|
||||||
|
except Exception:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
|
||||||
|
def badge_form(resource: dict[str, Any]) -> str:
|
||||||
|
payload = parse_json_value(str(resource.get("metadata_json") or "{}"), {})
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
return ""
|
||||||
|
return str(payload.get("badge_form") or "").strip().lower()
|
||||||
|
|
||||||
|
|
||||||
|
def is_vip_badge(resource: dict[str, Any]) -> bool:
|
||||||
|
code = str(resource.get("resource_code") or "").strip().lower()
|
||||||
|
name = str(resource.get("name") or "").strip().lower()
|
||||||
|
return str(resource.get("resource_type") or "") == "badge" and (code.startswith("vip") or name.startswith("vip"))
|
||||||
|
|
||||||
|
|
||||||
|
def build_catalog(resources: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
|
||||||
|
avatar_frames: list[dict[str, Any]] = []
|
||||||
|
long_badges: list[dict[str, Any]] = []
|
||||||
|
short_badges: list[dict[str, Any]] = []
|
||||||
|
for resource in resources:
|
||||||
|
resource_type = str(resource.get("resource_type") or "").strip()
|
||||||
|
resource_id = int(resource.get("resource_id") or 0)
|
||||||
|
if resource_type == "avatar_frame":
|
||||||
|
if resource_id in EXCLUDED_AVATAR_FRAME_IDS:
|
||||||
|
continue
|
||||||
|
if int(resource.get("coin_price") or 0) > ROBOT_AVATAR_FRAME_MAX_COIN_PRICE:
|
||||||
|
continue
|
||||||
|
avatar_frames.append(resource)
|
||||||
|
continue
|
||||||
|
if not is_vip_badge(resource):
|
||||||
|
continue
|
||||||
|
form = badge_form(resource)
|
||||||
|
if form in ("strip", "long"):
|
||||||
|
long_badges.append(resource)
|
||||||
|
elif form in ("tile", "short"):
|
||||||
|
short_badges.append(resource)
|
||||||
|
return {
|
||||||
|
"avatar_frame": avatar_frames,
|
||||||
|
"long_badge": long_badges,
|
||||||
|
"short_badge": short_badges,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def assert_catalog(catalog: dict[str, list[dict[str, Any]]]) -> None:
|
||||||
|
missing = [key for key, value in catalog.items() if not value]
|
||||||
|
if missing:
|
||||||
|
raise RuntimeError(f"robot appearance resource pool is incomplete: {', '.join(missing)}")
|
||||||
|
|
||||||
|
|
||||||
|
def choose_resource(items: list[dict[str, Any]], app_code: str, user_id: int, slot: str) -> dict[str, Any]:
|
||||||
|
index = int(stable_hash(f"{app_code}|{user_id}|{slot}")[:16], 16) % len(items)
|
||||||
|
return items[index]
|
||||||
|
|
||||||
|
|
||||||
|
def resource_snapshot(resource: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
scopes = parse_json_value(str(resource.get("usage_scope_json") or "[]"), [])
|
||||||
|
if not isinstance(scopes, list):
|
||||||
|
scopes = []
|
||||||
|
return {
|
||||||
|
"AppCode": str(resource.get("app_code") or ""),
|
||||||
|
"ResourceID": int(resource.get("resource_id") or 0),
|
||||||
|
"ResourceCode": str(resource.get("resource_code") or ""),
|
||||||
|
"ResourceType": str(resource.get("resource_type") or ""),
|
||||||
|
"Name": str(resource.get("name") or ""),
|
||||||
|
"Status": str(resource.get("status") or ""),
|
||||||
|
"Grantable": bool(resource.get("grantable")),
|
||||||
|
"GrantStrategy": str(resource.get("grant_strategy") or ""),
|
||||||
|
"WalletAssetType": str(resource.get("wallet_asset_type") or ""),
|
||||||
|
"WalletAssetAmount": int(resource.get("wallet_asset_amount") or 0),
|
||||||
|
"PriceType": str(resource.get("price_type") or ""),
|
||||||
|
"CoinPrice": int(resource.get("coin_price") or 0),
|
||||||
|
"GiftPointAmount": int(resource.get("gift_point_amount") or 0),
|
||||||
|
"UsageScopes": [str(item) for item in scopes],
|
||||||
|
"AssetURL": str(resource.get("asset_url") or ""),
|
||||||
|
"PreviewURL": str(resource.get("preview_url") or ""),
|
||||||
|
"AnimationURL": str(resource.get("animation_url") or ""),
|
||||||
|
"MetadataJSON": str(resource.get("metadata_json") or "{}"),
|
||||||
|
"SortOrder": int(resource.get("sort_order") or 0),
|
||||||
|
"CreatedByUserID": int(resource.get("created_by_user_id") or 0),
|
||||||
|
"UpdatedByUserID": int(resource.get("updated_by_user_id") or 0),
|
||||||
|
"CreatedAtMS": int(resource.get("created_at_ms") or 0),
|
||||||
|
"UpdatedAtMS": int(resource.get("updated_at_ms") or 0),
|
||||||
|
"ManagerGrantEnabled": bool(resource.get("manager_grant_enabled")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def grant_request_hash(app_code: str, user_id: int, resource_id: int, reason: str, operator_user_id: int) -> str:
|
||||||
|
return stable_hash(
|
||||||
|
f"resource_grant|{normalize_app_code(app_code)}|{user_id}|{resource_id}|1|{ROBOT_RESOURCE_DURATION_MS}|{reason}|{operator_user_id}|{GRANT_SOURCE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def resource_outbox_event_id(event_type: str, command_id: str, user_id: int, resource_id: int) -> str:
|
||||||
|
return "wev_" + stable_hash(f"{event_type}|{command_id}|{user_id}|{resource_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def insert_wallet_outbox(
|
||||||
|
cursor,
|
||||||
|
*,
|
||||||
|
app_code: str,
|
||||||
|
event_type: str,
|
||||||
|
command_id: str,
|
||||||
|
user_id: int,
|
||||||
|
resource_id: int,
|
||||||
|
payload: dict[str, Any],
|
||||||
|
now_ms: int,
|
||||||
|
) -> None:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT IGNORE INTO wallet_outbox (
|
||||||
|
app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type,
|
||||||
|
available_delta, frozen_delta, payload, status, retry_count, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (%s, %s, %s, '', %s, %s, %s, 0, 0, %s, %s, 0, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
app_code,
|
||||||
|
resource_outbox_event_id(event_type, command_id, user_id, resource_id),
|
||||||
|
event_type,
|
||||||
|
command_id,
|
||||||
|
user_id,
|
||||||
|
RESOURCE_OUTBOX_ASSET,
|
||||||
|
json.dumps(payload, ensure_ascii=False, separators=(",", ":")),
|
||||||
|
OUTBOX_STATUS_PENDING,
|
||||||
|
now_ms,
|
||||||
|
now_ms,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def active_entitlement(cursor, app_code: str, user_id: int, resource_id: int, now_ms: int) -> str:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT entitlement_id
|
||||||
|
FROM user_resource_entitlements
|
||||||
|
WHERE app_code = %s AND user_id = %s AND resource_id = %s
|
||||||
|
AND status = 'active'
|
||||||
|
AND effective_at_ms <= %s
|
||||||
|
AND (expires_at_ms = 0 OR expires_at_ms > %s)
|
||||||
|
AND remaining_quantity > 0
|
||||||
|
ORDER BY expires_at_ms DESC, created_at_ms DESC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
|
""",
|
||||||
|
(app_code, user_id, resource_id, now_ms, now_ms),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return str(row["entitlement_id"]) if row else ""
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_entitlement(
|
||||||
|
cursor,
|
||||||
|
*,
|
||||||
|
app_code: str,
|
||||||
|
user_id: int,
|
||||||
|
resource: dict[str, Any],
|
||||||
|
slot: str,
|
||||||
|
operator_user_id: int,
|
||||||
|
reason: str,
|
||||||
|
run_id: str,
|
||||||
|
now_ms: int,
|
||||||
|
) -> tuple[str, bool, str]:
|
||||||
|
resource_id = int(resource["resource_id"])
|
||||||
|
existing = active_entitlement(cursor, app_code, user_id, resource_id, now_ms)
|
||||||
|
if existing:
|
||||||
|
return existing, False, ""
|
||||||
|
|
||||||
|
command_id = f"robot_appearance_refresh:{run_id}:{user_id}:{slot}:{resource_id}"
|
||||||
|
grant_id = prefixed_id("rgr_", f"{normalize_app_code(app_code)}|{command_id}")
|
||||||
|
entitlement_id = prefixed_id("ent_", f"{normalize_app_code(app_code)}|{grant_id}|{resource_id}|{now_ms}")
|
||||||
|
snapshot = resource_snapshot(resource)
|
||||||
|
snapshot_json = json.dumps(snapshot, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
expires_at_ms = now_ms + ROBOT_RESOURCE_DURATION_MS
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO resource_grants (
|
||||||
|
app_code, grant_id, command_id, target_user_id, grant_source, grant_subject_type,
|
||||||
|
grant_subject_id, status, request_hash, group_snapshot_json, reason,
|
||||||
|
operator_user_id, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, 'resource', %s, %s, %s, NULL, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
app_code,
|
||||||
|
grant_id,
|
||||||
|
command_id,
|
||||||
|
user_id,
|
||||||
|
GRANT_SOURCE,
|
||||||
|
str(resource_id),
|
||||||
|
GRANT_STATUS_DONE,
|
||||||
|
grant_request_hash(app_code, user_id, resource_id, reason, operator_user_id),
|
||||||
|
reason,
|
||||||
|
operator_user_id,
|
||||||
|
now_ms,
|
||||||
|
now_ms,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_resource_entitlements (
|
||||||
|
app_code, entitlement_id, user_id, resource_id, status, quantity, remaining_quantity,
|
||||||
|
effective_at_ms, expires_at_ms, source_grant_id, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, 1, 1, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(app_code, entitlement_id, user_id, resource_id, ENTITLEMENT_STATUS_ACTIVE, now_ms, expires_at_ms, grant_id, now_ms, now_ms),
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO resource_grant_items (
|
||||||
|
app_code, grant_id, resource_id, resource_snapshot_json, quantity, duration_ms,
|
||||||
|
result_type, wallet_transaction_id, entitlement_id, created_at_ms
|
||||||
|
) VALUES (%s, %s, %s, %s, 1, %s, %s, '', %s, %s)
|
||||||
|
""",
|
||||||
|
(app_code, grant_id, resource_id, snapshot_json, ROBOT_RESOURCE_DURATION_MS, RESULT_ENTITLEMENT, entitlement_id, now_ms),
|
||||||
|
)
|
||||||
|
insert_wallet_outbox(
|
||||||
|
cursor,
|
||||||
|
app_code=app_code,
|
||||||
|
event_type="ResourceGranted",
|
||||||
|
command_id=command_id,
|
||||||
|
user_id=user_id,
|
||||||
|
resource_id=resource_id,
|
||||||
|
payload={"grant_id": grant_id, "resource": snapshot},
|
||||||
|
now_ms=now_ms,
|
||||||
|
)
|
||||||
|
return entitlement_id, True, grant_id
|
||||||
|
|
||||||
|
|
||||||
|
def entitlement_snapshot(
|
||||||
|
*,
|
||||||
|
app_code: str,
|
||||||
|
user_id: int,
|
||||||
|
entitlement_id: str,
|
||||||
|
resource: dict[str, Any],
|
||||||
|
grant_id: str,
|
||||||
|
now_ms: int,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"AppCode": app_code,
|
||||||
|
"EntitlementID": entitlement_id,
|
||||||
|
"UserID": user_id,
|
||||||
|
"ResourceID": int(resource["resource_id"]),
|
||||||
|
"Resource": resource_snapshot(resource),
|
||||||
|
"Status": ENTITLEMENT_STATUS_ACTIVE,
|
||||||
|
"Quantity": 1,
|
||||||
|
"RemainingQuantity": 1,
|
||||||
|
"EffectiveAtMS": now_ms,
|
||||||
|
"ExpiresAtMS": now_ms + ROBOT_RESOURCE_DURATION_MS,
|
||||||
|
"SourceGrantID": grant_id,
|
||||||
|
"CreatedAtMS": now_ms,
|
||||||
|
"UpdatedAtMS": now_ms,
|
||||||
|
"Equipped": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def replace_wallet_equipment(
|
||||||
|
conn,
|
||||||
|
*,
|
||||||
|
app_code: str,
|
||||||
|
user_id: int,
|
||||||
|
selected: dict[str, dict[str, Any]],
|
||||||
|
operator_user_id: int,
|
||||||
|
reason: str,
|
||||||
|
run_id: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
now_ms = current_ms()
|
||||||
|
created_grants = 0
|
||||||
|
equipped_rows = 0
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("START TRANSACTION")
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM user_resource_equipment
|
||||||
|
WHERE app_code = %s AND user_id = %s AND resource_type IN ('avatar_frame', 'badge')
|
||||||
|
""",
|
||||||
|
(app_code, user_id),
|
||||||
|
)
|
||||||
|
deleted_equipment = cursor.rowcount
|
||||||
|
for slot, resource in selected.items():
|
||||||
|
resource_type = str(resource["resource_type"])
|
||||||
|
resource_id = int(resource["resource_id"])
|
||||||
|
entitlement_id, created, grant_id = ensure_entitlement(
|
||||||
|
cursor,
|
||||||
|
app_code=app_code,
|
||||||
|
user_id=user_id,
|
||||||
|
resource=resource,
|
||||||
|
slot=slot,
|
||||||
|
operator_user_id=operator_user_id,
|
||||||
|
reason=reason,
|
||||||
|
run_id=run_id,
|
||||||
|
now_ms=now_ms,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
created_grants += 1
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_resource_equipment (
|
||||||
|
app_code, user_id, resource_type, entitlement_id, resource_id, updated_at_ms
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE resource_id = VALUES(resource_id), updated_at_ms = VALUES(updated_at_ms)
|
||||||
|
""",
|
||||||
|
(app_code, user_id, resource_type, entitlement_id, resource_id, now_ms),
|
||||||
|
)
|
||||||
|
equipped_rows += 1
|
||||||
|
event_command_id = f"robot_appearance_equip:{run_id}:{user_id}:{slot}:{resource_id}"
|
||||||
|
insert_wallet_outbox(
|
||||||
|
cursor,
|
||||||
|
app_code=app_code,
|
||||||
|
event_type="UserResourceChanged",
|
||||||
|
command_id=event_command_id,
|
||||||
|
user_id=user_id,
|
||||||
|
resource_id=resource_id,
|
||||||
|
payload={
|
||||||
|
"action": "equip",
|
||||||
|
"app_code": app_code,
|
||||||
|
"user_id": user_id,
|
||||||
|
"resource_id": resource_id,
|
||||||
|
"resource_type": resource_type,
|
||||||
|
"entitlement": entitlement_snapshot(
|
||||||
|
app_code=app_code,
|
||||||
|
user_id=user_id,
|
||||||
|
entitlement_id=entitlement_id,
|
||||||
|
resource=resource,
|
||||||
|
grant_id=grant_id,
|
||||||
|
now_ms=now_ms,
|
||||||
|
),
|
||||||
|
"updated_at_ms": now_ms,
|
||||||
|
"event_command": event_command_id,
|
||||||
|
"source": "robot_appearance_refresh",
|
||||||
|
},
|
||||||
|
now_ms=now_ms,
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return {"created_grants": created_grants, "equipped_rows": equipped_rows, "deleted_equipment": deleted_equipment}
|
||||||
|
except Exception:
|
||||||
|
conn.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
def replace_activity_badge_slots(
|
||||||
|
conn,
|
||||||
|
*,
|
||||||
|
app_code: str,
|
||||||
|
user_id: int,
|
||||||
|
selected: dict[str, dict[str, Any]],
|
||||||
|
wallet_conn,
|
||||||
|
) -> dict[str, int]:
|
||||||
|
now_ms = current_ms()
|
||||||
|
long_badge = selected["long_badge"]
|
||||||
|
short_badge = selected["short_badge"]
|
||||||
|
long_entitlement = latest_equipped_entitlement(wallet_conn, app_code, user_id, int(long_badge["resource_id"]))
|
||||||
|
short_entitlement = latest_equipped_entitlement(wallet_conn, app_code, user_id, int(short_badge["resource_id"]))
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("START TRANSACTION")
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
DELETE FROM user_badge_display_slots
|
||||||
|
WHERE app_code = %s AND user_id = %s AND slot IN ({','.join(['%s'] * len(BADGE_DISPLAY_SLOTS))})
|
||||||
|
""",
|
||||||
|
(app_code, user_id, *BADGE_DISPLAY_SLOTS),
|
||||||
|
)
|
||||||
|
deleted = cursor.rowcount
|
||||||
|
inserts = [
|
||||||
|
("profile_strip", 1, "strip", int(long_badge["resource_id"]), long_entitlement, "robot_appearance_refresh:long"),
|
||||||
|
("profile_tile", 1, "tile", int(short_badge["resource_id"]), short_entitlement, "robot_appearance_refresh:tile"),
|
||||||
|
("honor_wall", 1, "tile", int(short_badge["resource_id"]), short_entitlement, "robot_appearance_refresh:honor"),
|
||||||
|
]
|
||||||
|
for slot, position, form, resource_id, entitlement_id, source_id in inserts:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_badge_display_slots (
|
||||||
|
app_code, user_id, slot, position, badge_form, resource_id,
|
||||||
|
entitlement_id, source_type, source_id, pin_mode, updated_at_ms
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, 'system_grant', %s, 'auto', %s)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
badge_form = VALUES(badge_form),
|
||||||
|
resource_id = VALUES(resource_id),
|
||||||
|
entitlement_id = VALUES(entitlement_id),
|
||||||
|
source_type = VALUES(source_type),
|
||||||
|
source_id = VALUES(source_id),
|
||||||
|
pin_mode = VALUES(pin_mode),
|
||||||
|
updated_at_ms = VALUES(updated_at_ms)
|
||||||
|
""",
|
||||||
|
(app_code, user_id, slot, position, form, resource_id, entitlement_id, source_id, now_ms),
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
return {"deleted_slots": deleted, "inserted_slots": len(inserts)}
|
||||||
|
except Exception:
|
||||||
|
conn.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
|
def latest_equipped_entitlement(conn, app_code: str, user_id: int, resource_id: int) -> str:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT entitlement_id
|
||||||
|
FROM user_resource_equipment
|
||||||
|
WHERE app_code = %s AND user_id = %s AND resource_id = %s
|
||||||
|
ORDER BY updated_at_ms DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(app_code, user_id, resource_id),
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return str(row["entitlement_id"]) if row else ""
|
||||||
|
|
||||||
|
|
||||||
|
def equipment_summary(conn, app_code: str, user_ids: list[int], valid_frame_ids: set[int], valid_badge_ids: set[int]) -> dict[str, int]:
|
||||||
|
if not user_ids:
|
||||||
|
return {}
|
||||||
|
summary = {
|
||||||
|
"users_with_valid_avatar_frame": 0,
|
||||||
|
"invalid_avatar_frame_equipment_rows": 0,
|
||||||
|
"valid_badge_equipment_rows": 0,
|
||||||
|
"invalid_badge_equipment_rows": 0,
|
||||||
|
}
|
||||||
|
for chunk in chunks(user_ids, 500):
|
||||||
|
user_placeholders = ",".join(["%s"] * len(chunk))
|
||||||
|
frame_placeholders = ",".join(["%s"] * len(valid_frame_ids)) or "NULL"
|
||||||
|
badge_placeholders = ",".join(["%s"] * len(valid_badge_ids)) or "NULL"
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT CASE WHEN eq.resource_type = 'avatar_frame' AND eq.resource_id IN ({frame_placeholders}) THEN eq.user_id END) AS users_with_valid_avatar_frame,
|
||||||
|
SUM(CASE WHEN eq.resource_type = 'avatar_frame' AND eq.resource_id NOT IN ({frame_placeholders}) THEN 1 ELSE 0 END) AS invalid_avatar_frame_equipment_rows,
|
||||||
|
SUM(CASE WHEN eq.resource_type = 'badge' AND eq.resource_id IN ({badge_placeholders}) THEN 1 ELSE 0 END) AS valid_badge_equipment_rows,
|
||||||
|
SUM(CASE WHEN eq.resource_type = 'badge' AND eq.resource_id NOT IN ({badge_placeholders}) THEN 1 ELSE 0 END) AS invalid_badge_equipment_rows
|
||||||
|
FROM user_resource_equipment eq
|
||||||
|
WHERE eq.app_code = %s AND eq.user_id IN ({user_placeholders})
|
||||||
|
AND eq.resource_type IN ('avatar_frame', 'badge')
|
||||||
|
""",
|
||||||
|
[*valid_frame_ids, *valid_frame_ids, *valid_badge_ids, *valid_badge_ids, app_code, *chunk],
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
for key in summary:
|
||||||
|
summary[key] += int(row.get(key) or 0)
|
||||||
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def activity_slot_summary(conn, app_code: str, user_ids: list[int], valid_long_ids: set[int], valid_short_ids: set[int]) -> dict[str, int]:
|
||||||
|
if not user_ids:
|
||||||
|
return {}
|
||||||
|
valid_by_slot = {
|
||||||
|
"profile_strip": valid_long_ids,
|
||||||
|
"profile_tile": valid_short_ids,
|
||||||
|
"honor_wall": valid_short_ids,
|
||||||
|
}
|
||||||
|
summary = {f"{slot}_valid_users": 0 for slot in BADGE_DISPLAY_SLOTS}
|
||||||
|
summary.update({f"{slot}_invalid_rows": 0 for slot in BADGE_DISPLAY_SLOTS})
|
||||||
|
for slot, valid_ids in valid_by_slot.items():
|
||||||
|
for chunk in chunks(user_ids, 500):
|
||||||
|
user_placeholders = ",".join(["%s"] * len(chunk))
|
||||||
|
valid_placeholders = ",".join(["%s"] * len(valid_ids)) or "NULL"
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT CASE WHEN resource_id IN ({valid_placeholders}) THEN user_id END) AS valid_users,
|
||||||
|
SUM(CASE WHEN resource_id NOT IN ({valid_placeholders}) THEN 1 ELSE 0 END) AS invalid_rows
|
||||||
|
FROM user_badge_display_slots
|
||||||
|
WHERE app_code = %s AND user_id IN ({user_placeholders}) AND slot = %s
|
||||||
|
""",
|
||||||
|
[*valid_ids, *valid_ids, app_code, *chunk, slot],
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
summary[f"{slot}_valid_users"] += int(row.get("valid_users") or 0)
|
||||||
|
summary[f"{slot}_invalid_rows"] += int(row.get("invalid_rows") or 0)
|
||||||
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def chunks(values: list[int], size: int):
|
||||||
|
for index in range(0, len(values), size):
|
||||||
|
yield values[index : index + size]
|
||||||
|
|
||||||
|
|
||||||
|
def sample_assignments(app_code: str, user_ids: list[int], catalog: dict[str, list[dict[str, Any]]], limit: int = 10) -> list[dict[str, Any]]:
|
||||||
|
samples = []
|
||||||
|
for user_id in user_ids[:limit]:
|
||||||
|
selected = select_for_user(app_code, user_id, catalog)
|
||||||
|
samples.append(
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"avatar_frame": compact_resource(selected["avatar_frame"]),
|
||||||
|
"long_badge": compact_resource(selected["long_badge"]),
|
||||||
|
"short_badge": compact_resource(selected["short_badge"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return samples
|
||||||
|
|
||||||
|
|
||||||
|
def select_for_user(app_code: str, user_id: int, catalog: dict[str, list[dict[str, Any]]]) -> dict[str, dict[str, Any]]:
|
||||||
|
return {
|
||||||
|
"avatar_frame": choose_resource(catalog["avatar_frame"], app_code, user_id, "avatar_frame"),
|
||||||
|
"long_badge": choose_resource(catalog["long_badge"], app_code, user_id, "long_badge"),
|
||||||
|
"short_badge": choose_resource(catalog["short_badge"], app_code, user_id, "short_badge"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compact_resource(resource: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"resource_id": int(resource["resource_id"]),
|
||||||
|
"resource_code": resource["resource_code"],
|
||||||
|
"name": resource["name"],
|
||||||
|
"coin_price": int(resource.get("coin_price") or 0),
|
||||||
|
"badge_form": badge_form(resource),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Refresh all robot avatar frames and VIP badges.")
|
||||||
|
parser.add_argument("--user-config", default="services/user-service/configs/config.yaml")
|
||||||
|
parser.add_argument("--wallet-config", default="services/wallet-service/configs/config.yaml")
|
||||||
|
parser.add_argument("--robot-config", default="services/robot-service/configs/config.yaml")
|
||||||
|
parser.add_argument("--activity-config", default="services/activity-service/configs/config.yaml")
|
||||||
|
parser.add_argument("--app-code", default=APP_CODE_DEFAULT)
|
||||||
|
parser.add_argument("--robot-source", default=ROBOT_SOURCE_DEFAULT)
|
||||||
|
parser.add_argument("--operator-user-id", type=int, default=0)
|
||||||
|
parser.add_argument("--reason", default="refresh robot avatar frames and vip badges")
|
||||||
|
parser.add_argument("--progress-every", type=int, default=50)
|
||||||
|
parser.add_argument("--skip-activity", action="store_true")
|
||||||
|
parser.add_argument("--yes", action="store_true", help="Actually write wallet/activity changes.")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
app_code = normalize_app_code(args.app_code)
|
||||||
|
if args.yes and args.operator_user_id <= 0:
|
||||||
|
raise RuntimeError("--operator-user-id is required with --yes")
|
||||||
|
if args.yes and not args.reason.strip():
|
||||||
|
raise RuntimeError("--reason is required with --yes")
|
||||||
|
|
||||||
|
user_conn = connect_from_config(args.user_config)
|
||||||
|
wallet_conn = connect_from_config(args.wallet_config)
|
||||||
|
robot_conn = connect_from_config(args.robot_config) if args.robot_config.strip() else None
|
||||||
|
activity_conn = None if args.skip_activity else connect_from_config(args.activity_config)
|
||||||
|
try:
|
||||||
|
source_robots = fetch_user_source_robots(user_conn, app_code, args.robot_source.strip())
|
||||||
|
pool_robots = fetch_robot_pool_users(robot_conn, app_code) if robot_conn else {}
|
||||||
|
robots = filter_existing_users(user_conn, app_code, merge_robot_sources(source_robots, pool_robots))
|
||||||
|
user_ids = sorted(robots)
|
||||||
|
resources = fetch_resources(wallet_conn, app_code)
|
||||||
|
catalog = build_catalog(resources)
|
||||||
|
assert_catalog(catalog)
|
||||||
|
valid_frame_ids = {int(item["resource_id"]) for item in catalog["avatar_frame"]}
|
||||||
|
valid_long_ids = {int(item["resource_id"]) for item in catalog["long_badge"]}
|
||||||
|
valid_short_ids = {int(item["resource_id"]) for item in catalog["short_badge"]}
|
||||||
|
valid_badge_ids = valid_long_ids | valid_short_ids
|
||||||
|
|
||||||
|
before_wallet = equipment_summary(wallet_conn, app_code, user_ids, valid_frame_ids, valid_badge_ids)
|
||||||
|
before_activity = (
|
||||||
|
activity_slot_summary(activity_conn, app_code, user_ids, valid_long_ids, valid_short_ids) if activity_conn else {}
|
||||||
|
)
|
||||||
|
summary = {
|
||||||
|
"mode": "execute" if args.yes else "dry_run",
|
||||||
|
"app_code": app_code,
|
||||||
|
"robot_user_count": len(user_ids),
|
||||||
|
"source_user_count": len(source_robots),
|
||||||
|
"robot_pool_user_count": len(pool_robots),
|
||||||
|
"resource_pool": {key: [compact_resource(item) for item in value] for key, value in catalog.items()},
|
||||||
|
"before_wallet": before_wallet,
|
||||||
|
"before_activity": before_activity,
|
||||||
|
"sample_assignments": sample_assignments(app_code, user_ids, catalog),
|
||||||
|
}
|
||||||
|
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||||
|
if not args.yes:
|
||||||
|
print("dry-run only; rerun with --yes --operator-user-id <id> to write changes")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
run_id = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime()) + "_" + uuid.uuid4().hex[:12]
|
||||||
|
changed = {
|
||||||
|
"run_id": run_id,
|
||||||
|
"processed": 0,
|
||||||
|
"created_grants": 0,
|
||||||
|
"equipped_rows": 0,
|
||||||
|
"deleted_equipment": 0,
|
||||||
|
"deleted_activity_slots": 0,
|
||||||
|
"inserted_activity_slots": 0,
|
||||||
|
}
|
||||||
|
for user_id in user_ids:
|
||||||
|
selected = select_for_user(app_code, user_id, catalog)
|
||||||
|
wallet_result = replace_wallet_equipment(
|
||||||
|
wallet_conn,
|
||||||
|
app_code=app_code,
|
||||||
|
user_id=user_id,
|
||||||
|
selected=selected,
|
||||||
|
operator_user_id=args.operator_user_id,
|
||||||
|
reason=args.reason.strip(),
|
||||||
|
run_id=run_id,
|
||||||
|
)
|
||||||
|
changed["created_grants"] += wallet_result["created_grants"]
|
||||||
|
changed["equipped_rows"] += wallet_result["equipped_rows"]
|
||||||
|
changed["deleted_equipment"] += wallet_result["deleted_equipment"]
|
||||||
|
if activity_conn:
|
||||||
|
activity_result = replace_activity_badge_slots(
|
||||||
|
activity_conn,
|
||||||
|
app_code=app_code,
|
||||||
|
user_id=user_id,
|
||||||
|
selected=selected,
|
||||||
|
wallet_conn=wallet_conn,
|
||||||
|
)
|
||||||
|
changed["deleted_activity_slots"] += activity_result["deleted_slots"]
|
||||||
|
changed["inserted_activity_slots"] += activity_result["inserted_slots"]
|
||||||
|
changed["processed"] += 1
|
||||||
|
if args.progress_every > 0 and (changed["processed"] % args.progress_every == 0 or changed["processed"] == len(user_ids)):
|
||||||
|
print(json.dumps(changed, ensure_ascii=False))
|
||||||
|
|
||||||
|
after_wallet = equipment_summary(wallet_conn, app_code, user_ids, valid_frame_ids, valid_badge_ids)
|
||||||
|
after_activity = (
|
||||||
|
activity_slot_summary(activity_conn, app_code, user_ids, valid_long_ids, valid_short_ids) if activity_conn else {}
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"ok": True,
|
||||||
|
**changed,
|
||||||
|
"after_wallet": after_wallet,
|
||||||
|
"after_activity": after_activity,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
if robot_conn:
|
||||||
|
robot_conn.close()
|
||||||
|
if activity_conn:
|
||||||
|
activity_conn.close()
|
||||||
|
wallet_conn.close()
|
||||||
|
user_conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
coinledgermodule "hyapp-admin-server/internal/modules/coinledger"
|
coinledgermodule "hyapp-admin-server/internal/modules/coinledger"
|
||||||
countryregionmodule "hyapp-admin-server/internal/modules/countryregion"
|
countryregionmodule "hyapp-admin-server/internal/modules/countryregion"
|
||||||
cprelationmodule "hyapp-admin-server/internal/modules/cprelation"
|
cprelationmodule "hyapp-admin-server/internal/modules/cprelation"
|
||||||
|
cpweeklyrankmodule "hyapp-admin-server/internal/modules/cpweeklyrank"
|
||||||
cumulativerechargerewardmodule "hyapp-admin-server/internal/modules/cumulativerechargereward"
|
cumulativerechargerewardmodule "hyapp-admin-server/internal/modules/cumulativerechargereward"
|
||||||
dailytaskmodule "hyapp-admin-server/internal/modules/dailytask"
|
dailytaskmodule "hyapp-admin-server/internal/modules/dailytask"
|
||||||
dashboardmodule "hyapp-admin-server/internal/modules/dashboard"
|
dashboardmodule "hyapp-admin-server/internal/modules/dashboard"
|
||||||
@ -59,6 +60,7 @@ import (
|
|||||||
registrationrewardmodule "hyapp-admin-server/internal/modules/registrationreward"
|
registrationrewardmodule "hyapp-admin-server/internal/modules/registrationreward"
|
||||||
reportmodule "hyapp-admin-server/internal/modules/report"
|
reportmodule "hyapp-admin-server/internal/modules/report"
|
||||||
resourcemodule "hyapp-admin-server/internal/modules/resource"
|
resourcemodule "hyapp-admin-server/internal/modules/resource"
|
||||||
|
riskconfigmodule "hyapp-admin-server/internal/modules/riskconfig"
|
||||||
roomadminmodule "hyapp-admin-server/internal/modules/roomadmin"
|
roomadminmodule "hyapp-admin-server/internal/modules/roomadmin"
|
||||||
roomrocketmodule "hyapp-admin-server/internal/modules/roomrocket"
|
roomrocketmodule "hyapp-admin-server/internal/modules/roomrocket"
|
||||||
roomturnoverrewardmodule "hyapp-admin-server/internal/modules/roomturnoverreward"
|
roomturnoverrewardmodule "hyapp-admin-server/internal/modules/roomturnoverreward"
|
||||||
@ -263,6 +265,7 @@ func main() {
|
|||||||
CoinLedger: coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler),
|
CoinLedger: coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler),
|
||||||
CountryRegion: countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler),
|
CountryRegion: countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler),
|
||||||
CPRelation: cprelationmodule.NewWithActivity(userDB, activityclient.NewGRPC(activityConn), auditHandler),
|
CPRelation: cprelationmodule.NewWithActivity(userDB, activityclient.NewGRPC(activityConn), auditHandler),
|
||||||
|
CPWeeklyRank: cpweeklyrankmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
|
||||||
CumulativeRecharge: cumulativerechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
CumulativeRecharge: cumulativerechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
||||||
DailyTask: dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
|
DailyTask: dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
|
||||||
Dashboard: dashboardmodule.New(store, cfg, userclient.NewGRPC(userConn)),
|
Dashboard: dashboardmodule.New(store, cfg, userclient.NewGRPC(userConn)),
|
||||||
@ -293,6 +296,7 @@ func main() {
|
|||||||
RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
RegistrationReward: registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
||||||
RegionBlock: regionblockmodule.New(userDB, auditHandler),
|
RegionBlock: regionblockmodule.New(userDB, auditHandler),
|
||||||
Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler),
|
Resource: resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler),
|
||||||
|
RiskConfig: riskconfigmodule.New(userclient.NewGRPC(userConn), auditHandler),
|
||||||
RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler),
|
RoomAdmin: roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler),
|
||||||
RoomRocket: roomrocketmodule.New(roomClient, auditHandler),
|
RoomRocket: roomrocketmodule.New(roomClient, auditHandler),
|
||||||
RoomTurnoverReward: roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
RoomTurnoverReward: roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
|
||||||
|
|||||||
@ -288,6 +288,7 @@
|
|||||||
| `resource-group:update` | `button` | 编辑资源组、替换组成员、启用、禁用 |
|
| `resource-group:update` | `button` | 编辑资源组、替换组成员、启用、禁用 |
|
||||||
| `resource-grant:view` | `menu` | 资源赠送记录 |
|
| `resource-grant:view` | `menu` | 资源赠送记录 |
|
||||||
| `resource-grant:create` | `button` | 给用户赠送资源或资源组 |
|
| `resource-grant:create` | `button` | 给用户赠送资源或资源组 |
|
||||||
|
| `resource-grant:revoke` | `button` | 撤销成功的资源组赠送 |
|
||||||
| `gift:view` | `menu` | 礼物列表 |
|
| `gift:view` | `menu` | 礼物列表 |
|
||||||
| `gift:create` | `button` | 从资源库选择资源新增礼物 |
|
| `gift:create` | `button` | 从资源库选择资源新增礼物 |
|
||||||
| `gift:update` | `button` | 编辑礼物、资源绑定和价格 |
|
| `gift:update` | `button` | 编辑礼物、资源绑定和价格 |
|
||||||
|
|||||||
@ -3,6 +3,7 @@ module hyapp-admin-server
|
|||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
@ -10,7 +11,6 @@ require (
|
|||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.66
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.66
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.32.0
|
||||||
google.golang.org/grpc v1.68.0
|
google.golang.org/grpc v1.68.0
|
||||||
google.golang.org/protobuf v1.35.1
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
@ -57,4 +57,5 @@ require (
|
|||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.29.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
@ -59,6 +61,7 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
|||||||
@ -15,6 +15,8 @@ type Client interface {
|
|||||||
BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error)
|
BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error)
|
||||||
AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error)
|
AdminChangeUserCountry(ctx context.Context, req AdminChangeUserCountryRequest) (*ChangeUserCountryResult, error)
|
||||||
SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error)
|
SetUserStatus(ctx context.Context, req SetUserStatusRequest) (*SetUserStatusResult, error)
|
||||||
|
GetRegisterRiskConfig(ctx context.Context, req GetRegisterRiskConfigRequest) (*RegisterRiskConfig, error)
|
||||||
|
UpdateRegisterRiskConfig(ctx context.Context, req UpdateRegisterRiskConfigRequest) (*RegisterRiskConfig, error)
|
||||||
ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error)
|
ResolveDisplayUserID(ctx context.Context, req ResolveDisplayUserIDRequest) (*UserIdentity, error)
|
||||||
ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error)
|
ListPrettyDisplayIDPools(ctx context.Context, req ListPrettyDisplayIDPoolsRequest) ([]*PrettyDisplayIDPool, int64, error)
|
||||||
CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error)
|
CreatePrettyDisplayIDPool(ctx context.Context, req PrettyDisplayIDPoolRequest) (*PrettyDisplayIDPool, error)
|
||||||
@ -117,6 +119,25 @@ type SetUserStatusResult struct {
|
|||||||
RoomEvictError string `json:"roomEvictError,omitempty"`
|
RoomEvictError string `json:"roomEvictError,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetRegisterRiskConfigRequest struct {
|
||||||
|
RequestID string
|
||||||
|
Caller string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRegisterRiskConfigRequest struct {
|
||||||
|
RequestID string
|
||||||
|
Caller string
|
||||||
|
MaxAccountsPerDevice int32
|
||||||
|
OperatorAdminID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterRiskConfig struct {
|
||||||
|
AppCode string `json:"appCode"`
|
||||||
|
MaxAccountsPerDevice int32 `json:"maxAccountsPerDevice"`
|
||||||
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
|
UpdatedByAdminID int64 `json:"updatedByAdminId"`
|
||||||
|
}
|
||||||
|
|
||||||
type ResolveDisplayUserIDRequest struct {
|
type ResolveDisplayUserIDRequest struct {
|
||||||
RequestID string
|
RequestID string
|
||||||
Caller string
|
Caller string
|
||||||
@ -240,6 +261,28 @@ func (c *GRPCClient) GetUser(ctx context.Context, req GetUserRequest) (*User, er
|
|||||||
return fromProtoUser(resp.GetUser()), nil
|
return fromProtoUser(resp.GetUser()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) GetRegisterRiskConfig(ctx context.Context, req GetRegisterRiskConfigRequest) (*RegisterRiskConfig, error) {
|
||||||
|
resp, err := c.authClient.GetRegisterRiskConfig(ctx, &userv1.GetRegisterRiskConfigRequest{
|
||||||
|
Meta: requestMeta(ctx, req.RequestID, req.Caller),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromProtoRegisterRiskConfig(resp.GetConfig()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) UpdateRegisterRiskConfig(ctx context.Context, req UpdateRegisterRiskConfigRequest) (*RegisterRiskConfig, error) {
|
||||||
|
resp, err := c.authClient.UpdateRegisterRiskConfig(ctx, &userv1.UpdateRegisterRiskConfigRequest{
|
||||||
|
Meta: requestMeta(ctx, req.RequestID, req.Caller),
|
||||||
|
MaxAccountsPerDevice: req.MaxAccountsPerDevice,
|
||||||
|
OperatorAdminId: req.OperatorAdminID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromProtoRegisterRiskConfig(resp.GetConfig()), nil
|
||||||
|
}
|
||||||
|
|
||||||
// BatchGetUsers 复用 user-service 主数据 RPC,让后台列表只做展示聚合,不复制用户资料到游戏库。
|
// BatchGetUsers 复用 user-service 主数据 RPC,让后台列表只做展示聚合,不复制用户资料到游戏库。
|
||||||
func (c *GRPCClient) BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) {
|
func (c *GRPCClient) BatchGetUsers(ctx context.Context, req BatchGetUsersRequest) (map[int64]*User, error) {
|
||||||
resp, err := c.client.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{
|
resp, err := c.client.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{
|
||||||
@ -326,6 +369,18 @@ func (c *GRPCClient) ResolveDisplayUserID(ctx context.Context, req ResolveDispla
|
|||||||
return fromProtoUserIdentity(resp.GetIdentity()), nil
|
return fromProtoUserIdentity(resp.GetIdentity()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fromProtoRegisterRiskConfig(config *userv1.RegisterRiskConfig) *RegisterRiskConfig {
|
||||||
|
if config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &RegisterRiskConfig{
|
||||||
|
AppCode: config.GetAppCode(),
|
||||||
|
MaxAccountsPerDevice: config.GetMaxAccountsPerDevice(),
|
||||||
|
UpdatedAtMs: config.GetUpdatedAtMs(),
|
||||||
|
UpdatedByAdminID: config.GetUpdatedByAdminId(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fromProtoUser(user *userv1.User) *User {
|
func fromProtoUser(user *userv1.User) *User {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -488,71 +543,71 @@ type SetAgencyJoinEnabledRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BDProfile struct {
|
type BDProfile struct {
|
||||||
UserID int64 `json:"userId,string"`
|
UserID int64 `json:"userId,string"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
RegionID int64 `json:"regionId"`
|
RegionID int64 `json:"regionId"`
|
||||||
ParentLeaderUserID int64 `json:"parentLeaderUserId,string"`
|
ParentLeaderUserID int64 `json:"parentLeaderUserId,string"`
|
||||||
PositionAlias string `json:"positionAlias"`
|
PositionAlias string `json:"positionAlias"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CreatedByUserID int64 `json:"createdByUserId,string"`
|
CreatedByUserID int64 `json:"createdByUserId,string"`
|
||||||
CreatedAtMs int64 `json:"createdAtMs"`
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
DisplayUserID string `json:"displayUserId"`
|
DisplayUserID string `json:"displayUserId"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
RegionName string `json:"regionName"`
|
RegionName string `json:"regionName"`
|
||||||
ParentLeaderDisplayID string `json:"parentLeaderDisplayUserId"`
|
ParentLeaderDisplayID string `json:"parentLeaderDisplayUserId"`
|
||||||
ParentLeaderUsername string `json:"parentLeaderUsername"`
|
ParentLeaderUsername string `json:"parentLeaderUsername"`
|
||||||
ParentLeaderAvatar string `json:"parentLeaderAvatar"`
|
ParentLeaderAvatar string `json:"parentLeaderAvatar"`
|
||||||
CreatedByDisplayUserID string `json:"createdByDisplayUserId"`
|
CreatedByDisplayUserID string `json:"createdByDisplayUserId"`
|
||||||
CreatedByUsername string `json:"createdByUsername"`
|
CreatedByUsername string `json:"createdByUsername"`
|
||||||
CreatedByAvatar string `json:"createdByAvatar"`
|
CreatedByAvatar string `json:"createdByAvatar"`
|
||||||
SubBDCount int64 `json:"subBdCount"`
|
SubBDCount int64 `json:"subBdCount"`
|
||||||
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Agency struct {
|
type Agency struct {
|
||||||
AgencyID int64 `json:"agencyId,string"`
|
AgencyID int64 `json:"agencyId,string"`
|
||||||
OwnerUserID int64 `json:"ownerUserId,string"`
|
OwnerUserID int64 `json:"ownerUserId,string"`
|
||||||
RegionID int64 `json:"regionId"`
|
RegionID int64 `json:"regionId"`
|
||||||
ParentBDUserID int64 `json:"parentBdUserId,string"`
|
ParentBDUserID int64 `json:"parentBdUserId,string"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
JoinEnabled bool `json:"joinEnabled"`
|
JoinEnabled bool `json:"joinEnabled"`
|
||||||
MaxHosts int32 `json:"maxHosts"`
|
MaxHosts int32 `json:"maxHosts"`
|
||||||
CreatedByUserID int64 `json:"createdByUserId,string"`
|
CreatedByUserID int64 `json:"createdByUserId,string"`
|
||||||
CreatedAtMs int64 `json:"createdAtMs"`
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
OwnerDisplayUserID string `json:"ownerDisplayUserId"`
|
OwnerDisplayUserID string `json:"ownerDisplayUserId"`
|
||||||
OwnerUsername string `json:"ownerUsername"`
|
OwnerUsername string `json:"ownerUsername"`
|
||||||
OwnerAvatar string `json:"ownerAvatar"`
|
OwnerAvatar string `json:"ownerAvatar"`
|
||||||
ParentBDDisplayUserID string `json:"parentBdDisplayUserId"`
|
ParentBDDisplayUserID string `json:"parentBdDisplayUserId"`
|
||||||
ParentBDUsername string `json:"parentBdUsername"`
|
ParentBDUsername string `json:"parentBdUsername"`
|
||||||
ParentBDAvatar string `json:"parentBdAvatar"`
|
ParentBDAvatar string `json:"parentBdAvatar"`
|
||||||
RegionName string `json:"regionName"`
|
RegionName string `json:"regionName"`
|
||||||
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HostProfile struct {
|
type HostProfile struct {
|
||||||
UserID int64 `json:"userId,string"`
|
UserID int64 `json:"userId,string"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
RegionID int64 `json:"regionId"`
|
RegionID int64 `json:"regionId"`
|
||||||
CurrentAgencyID int64 `json:"currentAgencyId,string"`
|
CurrentAgencyID int64 `json:"currentAgencyId,string"`
|
||||||
CurrentMembershipID int64 `json:"currentMembershipId,string"`
|
CurrentMembershipID int64 `json:"currentMembershipId,string"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
FirstBecameHostAtMs int64 `json:"firstBecameHostAtMs"`
|
FirstBecameHostAtMs int64 `json:"firstBecameHostAtMs"`
|
||||||
CreatedAtMs int64 `json:"createdAtMs"`
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
DisplayUserID string `json:"displayUserId"`
|
DisplayUserID string `json:"displayUserId"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
RegionName string `json:"regionName"`
|
RegionName string `json:"regionName"`
|
||||||
CurrentAgencyName string `json:"currentAgencyName"`
|
CurrentAgencyName string `json:"currentAgencyName"`
|
||||||
Diamond int64 `json:"diamond"`
|
Diamond int64 `json:"diamond"`
|
||||||
CurrentAgencyOwnerUserID int64 `json:"currentAgencyOwnerUserId,string"`
|
CurrentAgencyOwnerUserID int64 `json:"currentAgencyOwnerUserId,string"`
|
||||||
CurrentAgencyOwnerDisplayUserID string `json:"currentAgencyOwnerDisplayUserId"`
|
CurrentAgencyOwnerDisplayUserID string `json:"currentAgencyOwnerDisplayUserId"`
|
||||||
CurrentAgencyOwnerUsername string `json:"currentAgencyOwnerUsername"`
|
CurrentAgencyOwnerUsername string `json:"currentAgencyOwnerUsername"`
|
||||||
CurrentAgencyOwnerAvatar string `json:"currentAgencyOwnerAvatar"`
|
CurrentAgencyOwnerAvatar string `json:"currentAgencyOwnerAvatar"`
|
||||||
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
SalaryWallet SalaryWallet `json:"salaryWallet"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ type Client interface {
|
|||||||
UpsertGiftTypeConfig(ctx context.Context, req *walletv1.UpsertGiftTypeConfigRequest) (*walletv1.GiftTypeConfigResponse, error)
|
UpsertGiftTypeConfig(ctx context.Context, req *walletv1.UpsertGiftTypeConfigRequest) (*walletv1.GiftTypeConfigResponse, error)
|
||||||
GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error)
|
GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest) (*walletv1.ResourceGrantResponse, error)
|
||||||
GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error)
|
GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest) (*walletv1.ResourceGrantResponse, error)
|
||||||
|
RevokeResourceGrant(ctx context.Context, req *walletv1.RevokeResourceGrantRequest) (*walletv1.ResourceGrantResponse, error)
|
||||||
EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error)
|
EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error)
|
||||||
ListResourceGrants(ctx context.Context, req *walletv1.ListResourceGrantsRequest) (*walletv1.ListResourceGrantsResponse, error)
|
ListResourceGrants(ctx context.Context, req *walletv1.ListResourceGrantsRequest) (*walletv1.ListResourceGrantsResponse, error)
|
||||||
ListResourceShopItems(ctx context.Context, req *walletv1.ListResourceShopItemsRequest) (*walletv1.ListResourceShopItemsResponse, error)
|
ListResourceShopItems(ctx context.Context, req *walletv1.ListResourceShopItemsRequest) (*walletv1.ListResourceShopItemsResponse, error)
|
||||||
@ -39,6 +40,8 @@ type Client interface {
|
|||||||
AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error)
|
AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error)
|
||||||
ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error)
|
ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error)
|
||||||
ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error)
|
ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error)
|
||||||
|
GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
|
ListTemporaryRechargeOrders(ctx context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error)
|
||||||
SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error)
|
SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error)
|
||||||
UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error)
|
UpdateThirdPartyPaymentRate(ctx context.Context, req *walletv1.UpdateThirdPartyPaymentRateRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error)
|
||||||
ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error)
|
ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error)
|
||||||
@ -139,6 +142,10 @@ func (c *GRPCClient) GrantResourceGroup(ctx context.Context, req *walletv1.Grant
|
|||||||
return c.client.GrantResourceGroup(ctx, req)
|
return c.client.GrantResourceGroup(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) RevokeResourceGrant(ctx context.Context, req *walletv1.RevokeResourceGrantRequest) (*walletv1.ResourceGrantResponse, error) {
|
||||||
|
return c.client.RevokeResourceGrant(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GRPCClient) EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) {
|
func (c *GRPCClient) EquipUserResource(ctx context.Context, req *walletv1.EquipUserResourceRequest) (*walletv1.EquipUserResourceResponse, error) {
|
||||||
return c.client.EquipUserResource(ctx, req)
|
return c.client.EquipUserResource(ctx, req)
|
||||||
}
|
}
|
||||||
@ -179,6 +186,14 @@ func (c *GRPCClient) ListThirdPartyPaymentChannels(ctx context.Context, req *wal
|
|||||||
return c.client.ListThirdPartyPaymentChannels(ctx, req)
|
return c.client.ListThirdPartyPaymentChannels(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
||||||
|
return c.client.GetTemporaryRechargeOrder(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) ListTemporaryRechargeOrders(ctx context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error) {
|
||||||
|
return c.client.ListTemporaryRechargeOrders(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GRPCClient) SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
|
func (c *GRPCClient) SetThirdPartyPaymentMethodStatus(ctx context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
|
||||||
return c.client.SetThirdPartyPaymentMethodStatus(ctx, req)
|
return c.client.SetThirdPartyPaymentMethodStatus(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,24 +33,67 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppUser struct {
|
type AppUser struct {
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Coin int64 `json:"coin"`
|
Balances []AppUserAssetBalance `json:"balances,omitempty"`
|
||||||
Country string `json:"country"`
|
Coin int64 `json:"coin"`
|
||||||
CountryDisplayName string `json:"countryDisplayName"`
|
Country string `json:"country"`
|
||||||
CountryName string `json:"countryName"`
|
CountryDisplayName string `json:"countryDisplayName"`
|
||||||
CreatedAtMs int64 `json:"createdAtMs"`
|
CountryName string `json:"countryName"`
|
||||||
DefaultDisplayUserID string `json:"defaultDisplayUserId"`
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
DisplayUserID string `json:"displayUserId"`
|
DefaultDisplayUserID string `json:"defaultDisplayUserId"`
|
||||||
Gender string `json:"gender"`
|
Diamond int64 `json:"diamond"`
|
||||||
LastActiveAtMs int64 `json:"lastActiveAtMs"`
|
DisplayUserID string `json:"displayUserId"`
|
||||||
PrettyDisplayUserID string `json:"prettyDisplayUserId"`
|
EquippedResources []AppUserResource `json:"equippedResources,omitempty"`
|
||||||
PrettyID string `json:"prettyId"`
|
Gender string `json:"gender"`
|
||||||
RegionID int64 `json:"regionId"`
|
LastActiveAtMs int64 `json:"lastActiveAtMs"`
|
||||||
RegionName string `json:"regionName"`
|
PrettyDisplayUserID string `json:"prettyDisplayUserId"`
|
||||||
Status string `json:"status"`
|
PrettyID string `json:"prettyId"`
|
||||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
RegionID int64 `json:"regionId"`
|
||||||
UserID string `json:"userId"`
|
RegionName string `json:"regionName"`
|
||||||
Username string `json:"username"`
|
Resources []AppUserResource `json:"resources,omitempty"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
VIP AppUserVIP `json:"vip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppUserAssetBalance struct {
|
||||||
|
AssetType string `json:"assetType"`
|
||||||
|
AvailableAmount int64 `json:"availableAmount"`
|
||||||
|
FrozenAmount int64 `json:"frozenAmount"`
|
||||||
|
TotalAmount int64 `json:"totalAmount"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppUserVIP struct {
|
||||||
|
Level int32 `json:"level"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
StartedAtMs int64 `json:"startedAtMs"`
|
||||||
|
ExpiresAtMs int64 `json:"expiresAtMs"`
|
||||||
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppUserResource struct {
|
||||||
|
EntitlementID string `json:"entitlementId"`
|
||||||
|
ResourceID int64 `json:"resourceId"`
|
||||||
|
ResourceCode string `json:"resourceCode"`
|
||||||
|
ResourceType string `json:"resourceType"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
RemainingQuantity int64 `json:"remainingQuantity"`
|
||||||
|
EffectiveAtMs int64 `json:"effectiveAtMs"`
|
||||||
|
ExpiresAtMs int64 `json:"expiresAtMs"`
|
||||||
|
SourceGrantID string `json:"sourceGrantId"`
|
||||||
|
AssetURL string `json:"assetUrl"`
|
||||||
|
PreviewURL string `json:"previewUrl"`
|
||||||
|
AnimationURL string `json:"animationUrl"`
|
||||||
|
Equipped bool `json:"equipped"`
|
||||||
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
|
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetUserStatusResult struct {
|
type SetUserStatusResult struct {
|
||||||
@ -146,6 +189,9 @@ func (s *Service) ListUsers(ctx context.Context, query listQuery) ([]AppUser, in
|
|||||||
if err := s.fillBalances(ctx, items, userIDs); err != nil {
|
if err := s.fillBalances(ctx, items, userIDs); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
if err := s.fillVIPLevels(ctx, items, userIDs); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +252,11 @@ func (s *Service) listUsersSortedByCoin(ctx context.Context, query listQuery, wh
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sortAppUsersByCoin(items, query.SortDirection)
|
sortAppUsersByCoin(items, query.SortDirection)
|
||||||
return paginateAppUsers(items, query.Page, query.PageSize), nil
|
pagedItems := paginateAppUsers(items, query.Page, query.PageSize)
|
||||||
|
if err := s.fillVIPLevels(ctx, pagedItems, appUserNumericIDs(pagedItems)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pagedItems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanAppUserRows(rows *sql.Rows, capacity int) ([]AppUser, []int64, error) {
|
func scanAppUserRows(rows *sql.Rows, capacity int) ([]AppUser, []int64, error) {
|
||||||
@ -327,6 +377,12 @@ func (s *Service) GetUser(ctx context.Context, userID int64) (AppUser, error) {
|
|||||||
if err := s.fillBalances(ctx, items, []int64{userID}); err != nil {
|
if err := s.fillBalances(ctx, items, []int64{userID}); err != nil {
|
||||||
return AppUser{}, err
|
return AppUser{}, err
|
||||||
}
|
}
|
||||||
|
if err := s.fillVIPLevels(ctx, items, []int64{userID}); err != nil {
|
||||||
|
return AppUser{}, err
|
||||||
|
}
|
||||||
|
if err := s.fillUserResources(ctx, items, userID); err != nil {
|
||||||
|
return AppUser{}, err
|
||||||
|
}
|
||||||
return items[0], nil
|
return items[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,26 +573,31 @@ func (s *Service) fillBalances(ctx context.Context, items []AppUser, userIDs []i
|
|||||||
}
|
}
|
||||||
// 用户列表金币排序会对完整筛选集补余额,按块查询可以避免 IN 参数过长导致后台列表失败。
|
// 用户列表金币排序会对完整筛选集补余额,按块查询可以避免 IN 参数过长导致后台列表失败。
|
||||||
chunk := userIDs[start:end]
|
chunk := userIDs[start:end]
|
||||||
placeholders := strings.TrimRight(strings.Repeat("?,", len(chunk)), ",")
|
args := make([]any, 0, len(chunk)+1)
|
||||||
args := make([]any, 0, len(chunk)+2)
|
|
||||||
args = append(args, appCode)
|
args = append(args, appCode)
|
||||||
for _, id := range chunk {
|
for _, id := range chunk {
|
||||||
args = append(args, id)
|
args = append(args, id)
|
||||||
}
|
}
|
||||||
args = append(args, "COIN")
|
|
||||||
rows, err := s.walletDB.QueryContext(ctx, fmt.Sprintf(`
|
rows, err := s.walletDB.QueryContext(ctx, fmt.Sprintf(`
|
||||||
SELECT user_id, asset_type, available_amount
|
SELECT user_id, asset_type, available_amount, frozen_amount, version, updated_at_ms
|
||||||
FROM wallet_accounts
|
FROM wallet_accounts
|
||||||
WHERE app_code = ? AND user_id IN (%s) AND asset_type = ?
|
WHERE app_code = ? AND user_id IN (%s)
|
||||||
`, placeholders), args...)
|
ORDER BY user_id ASC, asset_type ASC
|
||||||
|
`, placeholders(len(chunk))), args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var userID int64
|
var userID int64
|
||||||
var assetType string
|
var balance AppUserAssetBalance
|
||||||
var amount int64
|
if err := rows.Scan(
|
||||||
if err := rows.Scan(&userID, &assetType, &amount); err != nil {
|
&userID,
|
||||||
|
&balance.AssetType,
|
||||||
|
&balance.AvailableAmount,
|
||||||
|
&balance.FrozenAmount,
|
||||||
|
&balance.Version,
|
||||||
|
&balance.UpdatedAtMs,
|
||||||
|
); err != nil {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -544,9 +605,15 @@ func (s *Service) fillBalances(ctx context.Context, items []AppUser, userIDs []i
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch strings.ToUpper(assetType) {
|
balance.AssetType = strings.ToUpper(strings.TrimSpace(balance.AssetType))
|
||||||
|
balance.TotalAmount = balance.AvailableAmount + balance.FrozenAmount
|
||||||
|
// 钱包资产明细作为详情页“全部资产”的余额来源;金币和钻石仍同步到顶层字段,兼容列表排序和旧页面读取。
|
||||||
|
items[i].Balances = append(items[i].Balances, balance)
|
||||||
|
switch balance.AssetType {
|
||||||
case "COIN":
|
case "COIN":
|
||||||
items[i].Coin = amount
|
items[i].Coin = balance.AvailableAmount
|
||||||
|
case "DIAMOND":
|
||||||
|
items[i].Diamond = balance.AvailableAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
@ -560,6 +627,138 @@ func (s *Service) fillBalances(ctx context.Context, items []AppUser, userIDs []i
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) fillVIPLevels(ctx context.Context, items []AppUser, userIDs []int64) error {
|
||||||
|
if s.walletDB == nil || len(userIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
index := make(map[int64]int, len(userIDs))
|
||||||
|
for i, id := range userIDs {
|
||||||
|
index[id] = i
|
||||||
|
}
|
||||||
|
appCode := appctx.FromContext(ctx)
|
||||||
|
nowMs := nowMillis()
|
||||||
|
const chunkSize = 500
|
||||||
|
for start := 0; start < len(userIDs); start += chunkSize {
|
||||||
|
end := start + chunkSize
|
||||||
|
if end > len(userIDs) {
|
||||||
|
end = len(userIDs)
|
||||||
|
}
|
||||||
|
chunk := userIDs[start:end]
|
||||||
|
args := make([]any, 0, len(chunk)+1)
|
||||||
|
args = append(args, appCode)
|
||||||
|
for _, id := range chunk {
|
||||||
|
args = append(args, id)
|
||||||
|
}
|
||||||
|
rows, err := s.walletDB.QueryContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT m.user_id, m.level, COALESCE(v.name, m.name), m.started_at_ms, m.expires_at_ms, m.updated_at_ms
|
||||||
|
FROM user_vip_memberships m
|
||||||
|
LEFT JOIN vip_levels v ON v.app_code = m.app_code AND v.level = m.level
|
||||||
|
WHERE m.app_code = ? AND m.user_id IN (%s)
|
||||||
|
`, placeholders(len(chunk))), args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var userID int64
|
||||||
|
var vip AppUserVIP
|
||||||
|
if err := rows.Scan(&userID, &vip.Level, &vip.Name, &vip.StartedAtMs, &vip.ExpiresAtMs, &vip.UpdatedAtMs); err != nil {
|
||||||
|
_ = rows.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i, ok := index[userID]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 这里刻意沿用 wallet-service GetMyVip 的展示口径:只看 level 和 expires_at_ms 计算当前 VIP,
|
||||||
|
// 过期会员仍保留历史行,但后台用户列表不展示为有效等级。
|
||||||
|
vip.Active = vip.Level > 0 && vip.ExpiresAtMs > nowMs
|
||||||
|
if !vip.Active {
|
||||||
|
vip.Level = 0
|
||||||
|
vip.Name = ""
|
||||||
|
}
|
||||||
|
items[i].VIP = vip
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
_ = rows.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) fillUserResources(ctx context.Context, items []AppUser, userID int64) error {
|
||||||
|
if s.walletDB == nil || len(items) == 0 || userID <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
appCode := appctx.FromContext(ctx)
|
||||||
|
nowMs := nowMillis()
|
||||||
|
rows, err := s.walletDB.QueryContext(ctx, `
|
||||||
|
SELECT e.entitlement_id, e.resource_id, r.resource_code, r.resource_type, r.name, e.status,
|
||||||
|
e.quantity, e.remaining_quantity, e.effective_at_ms, e.expires_at_ms, e.source_grant_id,
|
||||||
|
COALESCE(r.asset_url, ''), COALESCE(r.preview_url, ''), COALESCE(r.animation_url, ''),
|
||||||
|
CASE WHEN eq.entitlement_id IS NULL THEN FALSE ELSE TRUE END AS equipped,
|
||||||
|
e.created_at_ms, e.updated_at_ms
|
||||||
|
FROM user_resource_entitlements e
|
||||||
|
JOIN resources r ON r.app_code = e.app_code AND r.resource_id = e.resource_id
|
||||||
|
LEFT JOIN user_resource_equipment eq
|
||||||
|
ON eq.app_code = e.app_code
|
||||||
|
AND eq.user_id = e.user_id
|
||||||
|
AND eq.resource_id = e.resource_id
|
||||||
|
AND eq.entitlement_id = e.entitlement_id
|
||||||
|
WHERE e.app_code = ? AND e.user_id = ?
|
||||||
|
AND e.status = 'active'
|
||||||
|
AND e.effective_at_ms <= ? AND (e.expires_at_ms = 0 OR e.expires_at_ms > ?)
|
||||||
|
AND e.remaining_quantity > 0
|
||||||
|
AND r.status = 'active'
|
||||||
|
ORDER BY equipped DESC, r.resource_type ASC, e.updated_at_ms DESC, e.created_at_ms DESC
|
||||||
|
`, appCode, userID, nowMs, nowMs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
resources := make([]AppUserResource, 0)
|
||||||
|
equipped := make([]AppUserResource, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var resource AppUserResource
|
||||||
|
if err := rows.Scan(
|
||||||
|
&resource.EntitlementID,
|
||||||
|
&resource.ResourceID,
|
||||||
|
&resource.ResourceCode,
|
||||||
|
&resource.ResourceType,
|
||||||
|
&resource.Name,
|
||||||
|
&resource.Status,
|
||||||
|
&resource.Quantity,
|
||||||
|
&resource.RemainingQuantity,
|
||||||
|
&resource.EffectiveAtMs,
|
||||||
|
&resource.ExpiresAtMs,
|
||||||
|
&resource.SourceGrantID,
|
||||||
|
&resource.AssetURL,
|
||||||
|
&resource.PreviewURL,
|
||||||
|
&resource.AnimationURL,
|
||||||
|
&resource.Equipped,
|
||||||
|
&resource.CreatedAtMs,
|
||||||
|
&resource.UpdatedAtMs,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resources = append(resources, resource)
|
||||||
|
if resource.Equipped {
|
||||||
|
// 装备状态只从 user_resource_equipment 读取;前端可以直接展示当前佩戴,不需要再按类型猜测。
|
||||||
|
equipped = append(equipped, resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
items[0].Resources = resources
|
||||||
|
items[0].EquippedResources = equipped
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeListQuery(query listQuery) listQuery {
|
func normalizeListQuery(query listQuery) listQuery {
|
||||||
if query.Page < 1 {
|
if query.Page < 1 {
|
||||||
query.Page = 1
|
query.Page = 1
|
||||||
@ -737,6 +936,18 @@ func paginateAppUsers(items []AppUser, page int, pageSize int) []AppUser {
|
|||||||
return items[start:end]
|
return items[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appUserNumericIDs(items []AppUser) []int64 {
|
||||||
|
ids := make([]int64, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
id, err := strconv.ParseInt(item.UserID, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
func countRows(ctx context.Context, db *sql.DB, whereSQL string, args ...any) (int64, error) {
|
func countRows(ctx context.Context, db *sql.DB, whereSQL string, args ...any) (int64, error) {
|
||||||
var total int64
|
var total int64
|
||||||
err := db.QueryRowContext(ctx, "SELECT COUNT(*) "+whereSQL, args...).Scan(&total)
|
err := db.QueryRowContext(ctx, "SELECT COUNT(*) "+whereSQL, args...).Scan(&total)
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const (
|
|||||||
coinSellerTransferBizType = "coin_seller_transfer"
|
coinSellerTransferBizType = "coin_seller_transfer"
|
||||||
coinSellerRechargeBizType = "coin_seller_recharge"
|
coinSellerRechargeBizType = "coin_seller_recharge"
|
||||||
coinSellerStockPurchaseBizType = "coin_seller_stock_purchase"
|
coinSellerStockPurchaseBizType = "coin_seller_stock_purchase"
|
||||||
|
coinSellerStockDeductionBizType = "coin_seller_stock_deduction"
|
||||||
coinSellerCoinCompensationBizType = "coin_seller_coin_compensation"
|
coinSellerCoinCompensationBizType = "coin_seller_coin_compensation"
|
||||||
salaryTransferToCoinSellerBizType = "salary_transfer_to_coin_seller"
|
salaryTransferToCoinSellerBizType = "salary_transfer_to_coin_seller"
|
||||||
coinSellerLedgerTypeAdminStockCredit = "admin_stock_credit"
|
coinSellerLedgerTypeAdminStockCredit = "admin_stock_credit"
|
||||||
@ -782,6 +783,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
// 空类型表示“全部币商流水”,但仍只允许当前产品定义的公开币商库存口径,不能把其他内部账带出来。
|
// 空类型表示“全部币商流水”,但仍只允许当前产品定义的公开币商库存口径,不能把其他内部账带出来。
|
||||||
return []string{
|
return []string{
|
||||||
coinSellerStockPurchaseBizType,
|
coinSellerStockPurchaseBizType,
|
||||||
|
coinSellerStockDeductionBizType,
|
||||||
coinSellerRechargeBizType,
|
coinSellerRechargeBizType,
|
||||||
coinSellerCoinCompensationBizType,
|
coinSellerCoinCompensationBizType,
|
||||||
coinSellerTransferBizType,
|
coinSellerTransferBizType,
|
||||||
@ -790,7 +792,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
case coinSellerLedgerTypeAdminStockCredit:
|
case coinSellerLedgerTypeAdminStockCredit:
|
||||||
// 进货操作是运营库存口径,底层包含后台 USDT 进货、H5 三方币商充值、金币补偿和后台扣除;资产条件仍限定在 COIN_SELLER_COIN。
|
// 进货操作是运营库存口径,底层包含后台 USDT 进货、H5 三方币商充值、金币补偿和后台扣除;资产条件仍限定在 COIN_SELLER_COIN。
|
||||||
return []string{coinSellerStockPurchaseBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType, coinManualCreditBizType}, nil
|
return []string{coinSellerStockPurchaseBizType, coinSellerStockDeductionBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType, coinManualCreditBizType}, nil
|
||||||
case coinSellerLedgerTypeSellerTransfer:
|
case coinSellerLedgerTypeSellerTransfer:
|
||||||
// 币商转用户只展示币商侧出账分录,收款用户资料在展示投影里补齐。
|
// 币商转用户只展示币商侧出账分录,收款用户资料在展示投影里补齐。
|
||||||
return []string{coinSellerTransferBizType}, nil
|
return []string{coinSellerTransferBizType}, nil
|
||||||
@ -804,7 +806,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
|
|
||||||
func coinSellerLedgerTypeForBizType(bizType string) string {
|
func coinSellerLedgerTypeForBizType(bizType string) string {
|
||||||
switch bizType {
|
switch bizType {
|
||||||
case coinSellerStockPurchaseBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType:
|
case coinSellerStockPurchaseBizType, coinSellerStockDeductionBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType:
|
||||||
return coinSellerLedgerTypeAdminStockCredit
|
return coinSellerLedgerTypeAdminStockCredit
|
||||||
case coinManualCreditBizType:
|
case coinManualCreditBizType:
|
||||||
return coinSellerLedgerTypeAdminStockCredit
|
return coinSellerLedgerTypeAdminStockCredit
|
||||||
@ -821,6 +823,8 @@ func coinSellerLedgerLabel(item coinSellerLedgerDTO) string {
|
|||||||
switch item.BizType {
|
switch item.BizType {
|
||||||
case coinSellerStockPurchaseBizType:
|
case coinSellerStockPurchaseBizType:
|
||||||
return "USDT进货"
|
return "USDT进货"
|
||||||
|
case coinSellerStockDeductionBizType:
|
||||||
|
return "USDT扣除"
|
||||||
case coinSellerRechargeBizType:
|
case coinSellerRechargeBizType:
|
||||||
return "三方充值"
|
return "三方充值"
|
||||||
case coinSellerCoinCompensationBizType:
|
case coinSellerCoinCompensationBizType:
|
||||||
|
|||||||
@ -51,10 +51,10 @@ func TestCoinSellerLedgerWhereMapsAdminStockCredit(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("coin seller ledger where failed: %v", err)
|
t.Fatalf("coin seller ledger where failed: %v", err)
|
||||||
}
|
}
|
||||||
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?) AND e.user_id IN (?,?)"; where != want {
|
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?) AND e.user_id IN (?,?)"; where != want {
|
||||||
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
||||||
}
|
}
|
||||||
if len(args) != 8 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerRechargeBizType || args[4] != coinSellerCoinCompensationBizType || args[5] != coinManualCreditBizType || args[6] != int64(3001) || args[7] != int64(3002) {
|
if len(args) != 9 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerStockDeductionBizType || args[4] != coinSellerRechargeBizType || args[5] != coinSellerCoinCompensationBizType || args[6] != coinManualCreditBizType || args[7] != int64(3001) || args[8] != int64(3002) {
|
||||||
t.Fatalf("args mismatch: %#v", args)
|
t.Fatalf("args mismatch: %#v", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,10 +64,10 @@ func TestCoinSellerLedgerWhereEmptyTypeUsesAllPublicTypes(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("coin seller ledger where failed: %v", err)
|
t.Fatalf("coin seller ledger where failed: %v", err)
|
||||||
}
|
}
|
||||||
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?,?)"; where != want {
|
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?,?,?)"; where != want {
|
||||||
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
||||||
}
|
}
|
||||||
if len(args) != 8 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerRechargeBizType || args[4] != coinSellerCoinCompensationBizType || args[5] != coinSellerTransferBizType || args[6] != salaryTransferToCoinSellerBizType || args[7] != coinManualCreditBizType {
|
if len(args) != 9 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerStockDeductionBizType || args[4] != coinSellerRechargeBizType || args[5] != coinSellerCoinCompensationBizType || args[6] != coinSellerTransferBizType || args[7] != salaryTransferToCoinSellerBizType || args[8] != coinManualCreditBizType {
|
||||||
t.Fatalf("args mismatch: %#v", args)
|
t.Fatalf("args mismatch: %#v", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +117,7 @@ func TestCoinSellerLedgerLabelUsesConcreteStockAndDebitType(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{name: "usdt purchase", item: coinSellerLedgerDTO{BizType: coinSellerStockPurchaseBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT进货"},
|
{name: "usdt purchase", item: coinSellerLedgerDTO{BizType: coinSellerStockPurchaseBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT进货"},
|
||||||
|
{name: "usdt deduction", item: coinSellerLedgerDTO{BizType: coinSellerStockDeductionBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT扣除"},
|
||||||
{name: "third party recharge", item: coinSellerLedgerDTO{BizType: coinSellerRechargeBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "三方充值"},
|
{name: "third party recharge", item: coinSellerLedgerDTO{BizType: coinSellerRechargeBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "三方充值"},
|
||||||
{name: "compensation", item: coinSellerLedgerDTO{BizType: coinSellerCoinCompensationBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "金币补偿"},
|
{name: "compensation", item: coinSellerLedgerDTO{BizType: coinSellerCoinCompensationBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "金币补偿"},
|
||||||
{name: "debit", item: coinSellerLedgerDTO{BizType: coinManualCreditBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit, AvailableDelta: -100}, want: "金币扣除"},
|
{name: "debit", item: coinSellerLedgerDTO{BizType: coinManualCreditBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit, AvailableDelta: -100}, want: "金币扣除"},
|
||||||
|
|||||||
@ -68,6 +68,11 @@ func (h *Handler) UpdateConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.WeeklyRank != nil {
|
if req.WeeklyRank != nil {
|
||||||
|
// CP 周榜已经拆成独立活动入口;旧接口保留兼容字段,但写入必须同时具备周榜活动权限,避免只拿到关系配置权限的后台用户绕过新菜单。
|
||||||
|
if !middleware.HasPermission(c, "cp-weekly-rank:update") {
|
||||||
|
response.Forbidden(c, "没有操作权限")
|
||||||
|
return
|
||||||
|
}
|
||||||
req.WeeklyRank.UpdatedByAdminID = int64(middleware.CurrentUserID(c))
|
req.WeeklyRank.UpdatedByAdminID = int64(middleware.CurrentUserID(c))
|
||||||
}
|
}
|
||||||
config, err := h.service.UpdateConfig(c.Request.Context(), appctx.FromContext(c.Request.Context()), req)
|
config, err := h.service.UpdateConfig(c.Request.Context(), appctx.FromContext(c.Request.Context()), req)
|
||||||
|
|||||||
192
server/admin/internal/modules/cpweeklyrank/handler.go
Normal file
192
server/admin/internal/modules/cpweeklyrank/handler.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package cpweeklyrank
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/appctx"
|
||||||
|
"hyapp-admin-server/internal/integration/activityclient"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
activity activityclient.Client
|
||||||
|
audit shared.OperationLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(activity activityclient.Client, audit shared.OperationLogger) *Handler {
|
||||||
|
return &Handler{activity: activity, audit: audit}
|
||||||
|
}
|
||||||
|
|
||||||
|
type configRequest struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
TopCount int32 `json:"top_count"`
|
||||||
|
Rewards []rewardDTO `json:"rewards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type configDTO struct {
|
||||||
|
AppCode string `json:"app_code"`
|
||||||
|
ActivityCode string `json:"activity_code"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
RelationType string `json:"relation_type"`
|
||||||
|
TopCount int32 `json:"top_count"`
|
||||||
|
Rewards []rewardDTO `json:"rewards"`
|
||||||
|
UpdatedByAdminID int64 `json:"updated_by_admin_id,string"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rewardDTO struct {
|
||||||
|
RankNo int32 `json:"rank_no"`
|
||||||
|
ResourceGroupID int64 `json:"resource_group_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type settlementDTO struct {
|
||||||
|
SettlementID string `json:"settlement_id"`
|
||||||
|
PeriodStartMS int64 `json:"period_start_ms"`
|
||||||
|
PeriodEndMS int64 `json:"period_end_ms"`
|
||||||
|
RankNo int32 `json:"rank_no"`
|
||||||
|
RelationshipID string `json:"relationship_id"`
|
||||||
|
UserID int64 `json:"user_id,string"`
|
||||||
|
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"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetConfig(c *gin.Context) {
|
||||||
|
resp, err := h.activity.GetCPWeeklyRankConfig(c.Request.Context(), &activityv1.GetCPWeeklyRankConfigRequest{Meta: h.meta(c)})
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "获取CP排行活动配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, configFromProto(resp.GetConfig()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UpdateConfig(c *gin.Context) {
|
||||||
|
var req configRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "CP排行活动配置参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.activity.UpdateCPWeeklyRankConfig(c.Request.Context(), &activityv1.UpdateCPWeeklyRankConfigRequest{
|
||||||
|
Meta: h.meta(c),
|
||||||
|
Config: req.toProto(),
|
||||||
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item := configFromProto(resp.GetConfig())
|
||||||
|
shared.OperationLogWithResourceID(c, h.audit, "update-cp-weekly-rank-config", "cp_weekly_rank_configs", item.AppCode, "success", "")
|
||||||
|
response.OK(c, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ListSettlements(c *gin.Context) {
|
||||||
|
periodStartMS := parseInt64Query(c, "period_start_ms")
|
||||||
|
periodEndMS := parseInt64Query(c, "period_end_ms")
|
||||||
|
resp, err := h.activity.ListCPWeeklyRankSettlements(c.Request.Context(), &activityv1.ListCPWeeklyRankSettlementsRequest{
|
||||||
|
Meta: h.meta(c),
|
||||||
|
PeriodStartMs: periodStartMS,
|
||||||
|
PeriodEndMs: periodEndMS,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "获取CP排行活动发奖记录失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items := make([]settlementDTO, 0, len(resp.GetSettlements()))
|
||||||
|
for _, settlement := range resp.GetSettlements() {
|
||||||
|
items = append(items, settlementFromProto(settlement))
|
||||||
|
}
|
||||||
|
response.OK(c, gin.H{"items": items, "total": len(items), "period_start_ms": periodStartMS, "period_end_ms": periodEndMS})
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r configRequest) toProto() *activityv1.CPWeeklyRankConfig {
|
||||||
|
topCount := r.TopCount
|
||||||
|
if topCount <= 0 {
|
||||||
|
topCount = 3
|
||||||
|
}
|
||||||
|
rewards := make([]*activityv1.CPWeeklyRankReward, 0, len(r.Rewards))
|
||||||
|
for _, reward := range r.Rewards {
|
||||||
|
// activity-service 会按 top_count 归一化和校验;admin-server 只做传输层适配,
|
||||||
|
// 保留原 rank/resource_group 组合,避免两个服务出现不同的配置裁决口径。
|
||||||
|
rewards = append(rewards, &activityv1.CPWeeklyRankReward{RankNo: reward.RankNo, ResourceGroupId: reward.ResourceGroupID})
|
||||||
|
}
|
||||||
|
return &activityv1.CPWeeklyRankConfig{
|
||||||
|
ActivityCode: "cp_weekly_rank",
|
||||||
|
Enabled: r.Enabled,
|
||||||
|
RelationType: "cp",
|
||||||
|
TopCount: topCount,
|
||||||
|
Rewards: rewards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configFromProto(config *activityv1.CPWeeklyRankConfig) configDTO {
|
||||||
|
if config == nil {
|
||||||
|
return configDTO{ActivityCode: "cp_weekly_rank", Enabled: true, RelationType: "cp", TopCount: 3, Rewards: []rewardDTO{}}
|
||||||
|
}
|
||||||
|
rewards := make([]rewardDTO, 0, len(config.GetRewards()))
|
||||||
|
for _, reward := range config.GetRewards() {
|
||||||
|
rewards = append(rewards, rewardDTO{RankNo: reward.GetRankNo(), ResourceGroupID: reward.GetResourceGroupId()})
|
||||||
|
}
|
||||||
|
return configDTO{
|
||||||
|
AppCode: config.GetAppCode(),
|
||||||
|
ActivityCode: config.GetActivityCode(),
|
||||||
|
Enabled: config.GetEnabled(),
|
||||||
|
RelationType: config.GetRelationType(),
|
||||||
|
TopCount: config.GetTopCount(),
|
||||||
|
Rewards: rewards,
|
||||||
|
UpdatedByAdminID: config.GetUpdatedByAdminId(),
|
||||||
|
CreatedAtMS: config.GetCreatedAtMs(),
|
||||||
|
UpdatedAtMS: config.GetUpdatedAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func settlementFromProto(settlement *activityv1.CPWeeklyRankSettlement) settlementDTO {
|
||||||
|
if settlement == nil {
|
||||||
|
return settlementDTO{}
|
||||||
|
}
|
||||||
|
return settlementDTO{
|
||||||
|
SettlementID: settlement.GetSettlementId(),
|
||||||
|
PeriodStartMS: settlement.GetPeriodStartMs(),
|
||||||
|
PeriodEndMS: settlement.GetPeriodEndMs(),
|
||||||
|
RankNo: settlement.GetRankNo(),
|
||||||
|
RelationshipID: settlement.GetRelationshipId(),
|
||||||
|
UserID: settlement.GetUserId(),
|
||||||
|
Score: settlement.GetScore(),
|
||||||
|
ResourceGroupID: settlement.GetResourceGroupId(),
|
||||||
|
WalletCommandID: settlement.GetWalletCommandId(),
|
||||||
|
WalletGrantID: settlement.GetWalletGrantId(),
|
||||||
|
Status: settlement.GetStatus(),
|
||||||
|
FailureReason: settlement.GetFailureReason(),
|
||||||
|
AttemptCount: settlement.GetAttemptCount(),
|
||||||
|
CreatedAtMS: settlement.GetCreatedAtMs(),
|
||||||
|
UpdatedAtMS: settlement.GetUpdatedAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt64Query(c *gin.Context, key string) int64 {
|
||||||
|
value, _ := strconv.ParseInt(strings.TrimSpace(c.Query(key)), 10, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
16
server/admin/internal/modules/cpweeklyrank/routes.go
Normal file
16
server/admin/internal/modules/cpweeklyrank/routes.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package cpweeklyrank
|
||||||
|
|
||||||
|
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/activity/cp-weekly-rank/config", middleware.RequirePermission("cp-weekly-rank:view"), h.GetConfig)
|
||||||
|
protected.PUT("/admin/activity/cp-weekly-rank/config", middleware.RequirePermission("cp-weekly-rank:update"), h.UpdateConfig)
|
||||||
|
protected.GET("/admin/activity/cp-weekly-rank/settlements", middleware.RequirePermission("cp-weekly-rank:view"), h.ListSettlements)
|
||||||
|
}
|
||||||
@ -176,38 +176,12 @@ func (h *Handler) resolveGrantUserID(ctx context.Context, keyword string) (int64
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
// 累计充值奖励按 activity-service 的内部 user_id 过滤;keyword 的长 ID、短号、靓号、昵称优先级由 shared 统一维护。
|
||||||
return 0, false, true
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
}
|
|
||||||
// keyword 支持真实 user_id、短 ID 和昵称模糊匹配;排序优先精确 ID,避免昵称碰撞查到错误用户。
|
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillGrantUsers(ctx context.Context, grants []grantDTO) error {
|
func (h *Handler) fillGrantUsers(ctx context.Context, grants []grantDTO) error {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ func (h *Handler) DashboardOverview(c *gin.Context) {
|
|||||||
func (h *Handler) StatisticsOverview(c *gin.Context) {
|
func (h *Handler) StatisticsOverview(c *gin.Context) {
|
||||||
overview, err := h.service.StatisticsOverview(c.Request.Context(), StatisticsQuery{
|
overview, err := h.service.StatisticsOverview(c.Request.Context(), StatisticsQuery{
|
||||||
AppCode: c.DefaultQuery("app_code", "lalu"),
|
AppCode: c.DefaultQuery("app_code", "lalu"),
|
||||||
|
StatTZ: c.Query("stat_tz"),
|
||||||
StartMS: parseInt64(c.Query("start_ms")),
|
StartMS: parseInt64(c.Query("start_ms")),
|
||||||
EndMS: parseInt64(c.Query("end_ms")),
|
EndMS: parseInt64(c.Query("end_ms")),
|
||||||
SeriesStartMS: parseInt64(c.Query("series_start_ms")),
|
SeriesStartMS: parseInt64(c.Query("series_start_ms")),
|
||||||
@ -48,6 +49,7 @@ func (h *Handler) StatisticsOverview(c *gin.Context) {
|
|||||||
func (h *Handler) SelfGameStatisticsOverview(c *gin.Context) {
|
func (h *Handler) SelfGameStatisticsOverview(c *gin.Context) {
|
||||||
overview, err := h.service.SelfGameStatisticsOverview(c.Request.Context(), SelfGameStatisticsQuery{
|
overview, err := h.service.SelfGameStatisticsOverview(c.Request.Context(), SelfGameStatisticsQuery{
|
||||||
AppCode: c.DefaultQuery("app_code", "lalu"),
|
AppCode: c.DefaultQuery("app_code", "lalu"),
|
||||||
|
StatTZ: c.Query("stat_tz"),
|
||||||
RequestID: middleware.CurrentRequestID(c),
|
RequestID: middleware.CurrentRequestID(c),
|
||||||
StartMS: parseInt64(c.Query("start_ms")),
|
StartMS: parseInt64(c.Query("start_ms")),
|
||||||
EndMS: parseInt64(c.Query("end_ms")),
|
EndMS: parseInt64(c.Query("end_ms")),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type DashboardService struct {
|
|||||||
|
|
||||||
type StatisticsQuery struct {
|
type StatisticsQuery struct {
|
||||||
AppCode string
|
AppCode string
|
||||||
|
StatTZ string
|
||||||
StartMS int64
|
StartMS int64
|
||||||
EndMS int64
|
EndMS int64
|
||||||
SeriesStartMS int64
|
SeriesStartMS int64
|
||||||
@ -33,6 +34,7 @@ type StatisticsQuery struct {
|
|||||||
|
|
||||||
type SelfGameStatisticsQuery struct {
|
type SelfGameStatisticsQuery struct {
|
||||||
AppCode string
|
AppCode string
|
||||||
|
StatTZ string
|
||||||
RequestID string
|
RequestID string
|
||||||
StartMS int64
|
StartMS int64
|
||||||
EndMS int64
|
EndMS int64
|
||||||
@ -57,6 +59,9 @@ func (s *DashboardService) Overview() (*repository.DashboardOverview, error) {
|
|||||||
func (s *DashboardService) StatisticsOverview(ctx context.Context, query StatisticsQuery) (map[string]any, error) {
|
func (s *DashboardService) StatisticsOverview(ctx context.Context, query StatisticsQuery) (map[string]any, error) {
|
||||||
values := url.Values{}
|
values := url.Values{}
|
||||||
values.Set("app_code", query.AppCode)
|
values.Set("app_code", query.AppCode)
|
||||||
|
if statTZ := strings.TrimSpace(query.StatTZ); statTZ != "" {
|
||||||
|
values.Set("stat_tz", statTZ)
|
||||||
|
}
|
||||||
if query.StartMS > 0 {
|
if query.StartMS > 0 {
|
||||||
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
|
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
|
||||||
}
|
}
|
||||||
@ -81,6 +86,9 @@ func (s *DashboardService) StatisticsOverview(ctx context.Context, query Statist
|
|||||||
func (s *DashboardService) SelfGameStatisticsOverview(ctx context.Context, query SelfGameStatisticsQuery) (map[string]any, error) {
|
func (s *DashboardService) SelfGameStatisticsOverview(ctx context.Context, query SelfGameStatisticsQuery) (map[string]any, error) {
|
||||||
values := url.Values{}
|
values := url.Values{}
|
||||||
values.Set("app_code", query.AppCode)
|
values.Set("app_code", query.AppCode)
|
||||||
|
if statTZ := strings.TrimSpace(query.StatTZ); statTZ != "" {
|
||||||
|
values.Set("stat_tz", statTZ)
|
||||||
|
}
|
||||||
if query.StartMS > 0 {
|
if query.StartMS > 0 {
|
||||||
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
|
values.Set("start_ms", strconv.FormatInt(query.StartMS, 10))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -181,37 +181,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
// 奖励领取记录由 activity-service 按内部 user_id 过滤;后台关键词先统一解析,避免各活动模块复制短号/靓号 SQL。
|
||||||
return 0, false, true
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
}
|
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const (
|
|||||||
robotResourceDurationMS = int64(robotResourceDurationDays) * int64(24*time.Hour/time.Millisecond)
|
robotResourceDurationMS = int64(robotResourceDurationDays) * int64(24*time.Hour/time.Millisecond)
|
||||||
robotMinDisplayLevel = 5
|
robotMinDisplayLevel = 5
|
||||||
robotMaxDisplayLevel = 30
|
robotMaxDisplayLevel = 30
|
||||||
|
robotMaxAvatarFrameCoins = 2_000_000
|
||||||
)
|
)
|
||||||
|
|
||||||
var excludedRobotAvatarFrameResourceIDs = map[int64]struct{}{
|
var excludedRobotAvatarFrameResourceIDs = map[int64]struct{}{
|
||||||
@ -83,6 +84,11 @@ func (h *Handler) loadRobotAppearanceCatalog(ctx context.Context, requestID stri
|
|||||||
}
|
}
|
||||||
catalog := robotAppearanceCatalog{avatarFrames: avatarFrames, vehicles: vehicles}
|
catalog := robotAppearanceCatalog{avatarFrames: avatarFrames, vehicles: vehicles}
|
||||||
for _, badge := range badges {
|
for _, badge := range badges {
|
||||||
|
if !isRobotVIPBadgeResource(badge) {
|
||||||
|
// 机器人批量账号只使用 VIP 体系徽章;普通活动、成就和运营徽章仍留给真实用户或明确活动链路,
|
||||||
|
// 避免机器人大量占用非 VIP 资源造成客户端误判身份来源。
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch robotBadgeForm(badge.GetMetadataJson()) {
|
switch robotBadgeForm(badge.GetMetadataJson()) {
|
||||||
case "strip", "long":
|
case "strip", "long":
|
||||||
catalog.longBadges = append(catalog.longBadges, badge)
|
catalog.longBadges = append(catalog.longBadges, badge)
|
||||||
@ -115,6 +121,11 @@ func (h *Handler) listRobotResources(ctx context.Context, requestID string, appC
|
|||||||
// 真人发放、商城和其他后台场景不受这个创建机器人专用策略影响。
|
// 真人发放、商城和其他后台场景不受这个创建机器人专用策略影响。
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if resourceType == "avatar_frame" && item.GetCoinPrice() > robotMaxAvatarFrameCoins {
|
||||||
|
// 头像框用金币价格做机器人随机池硬边界;价格高于 2000000 的资源通常是稀有或运营保留展示,
|
||||||
|
// 创建机器人时不应自动发放,线上批量修复脚本也复用同一条规则。
|
||||||
|
continue
|
||||||
|
}
|
||||||
resources = append(resources, item)
|
resources = append(resources, item)
|
||||||
}
|
}
|
||||||
return resources, nil
|
return resources, nil
|
||||||
@ -128,6 +139,15 @@ func isExcludedRobotAppearanceResource(resourceType string, resourceID int64) bo
|
|||||||
return excluded
|
return excluded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isRobotVIPBadgeResource(resource *walletv1.Resource) bool {
|
||||||
|
if resource == nil || resource.GetResourceType() != "badge" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
code := strings.ToLower(strings.TrimSpace(resource.GetResourceCode()))
|
||||||
|
name := strings.ToLower(strings.TrimSpace(resource.GetName()))
|
||||||
|
return strings.HasPrefix(code, "vip") || strings.HasPrefix(name, "vip")
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) grantAndEquipRobotResource(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, slot string, resourceID int64, equip bool) error {
|
func (h *Handler) grantAndEquipRobotResource(ctx context.Context, requestID string, appCode string, actorUserID int64, userID int64, slot string, resourceID int64, equip bool) error {
|
||||||
grant, err := h.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{
|
grant, err := h.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{
|
||||||
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, slot, resourceID),
|
CommandId: fmt.Sprintf("game_robot_init:%s:%d:%s:%d", requestID, userID, slot, resourceID),
|
||||||
|
|||||||
@ -75,16 +75,19 @@ func (c *fakeRobotWalletClient) ListResources(_ context.Context, req *walletv1.L
|
|||||||
switch req.GetResourceType() {
|
switch req.GetResourceType() {
|
||||||
case "avatar_frame":
|
case "avatar_frame":
|
||||||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
||||||
{ResourceId: 165, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false},
|
{ResourceId: 165, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: 1_000_000},
|
||||||
{ResourceId: 175, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false},
|
{ResourceId: 175, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: 1_500_000},
|
||||||
{ResourceId: 101, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false},
|
{ResourceId: 101, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: robotMaxAvatarFrameCoins},
|
||||||
|
{ResourceId: 176, ResourceType: "avatar_frame", Status: "active", ManagerGrantEnabled: false, CoinPrice: robotMaxAvatarFrameCoins + 1},
|
||||||
}}, nil
|
}}, nil
|
||||||
case "vehicle":
|
case "vehicle":
|
||||||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{{ResourceId: 201, ResourceType: "vehicle", Status: "active", ManagerGrantEnabled: false}}}, nil
|
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{{ResourceId: 201, ResourceType: "vehicle", Status: "active", ManagerGrantEnabled: false}}}, nil
|
||||||
case "badge":
|
case "badge":
|
||||||
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
return &walletv1.ListResourcesResponse{Resources: []*walletv1.Resource{
|
||||||
{ResourceId: 301, ResourceType: "badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
|
{ResourceId: 301, ResourceCode: "event_host_strip", ResourceType: "badge", Name: "Host Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
|
||||||
{ResourceId: 302, ResourceType: "badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
|
{ResourceId: 302, ResourceCode: "activity_tile", ResourceType: "badge", Name: "Activity Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
|
||||||
|
{ResourceId: 303, ResourceCode: "VIP1_Long_Badge", ResourceType: "badge", Name: "VIP1_Long Badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"strip"}`},
|
||||||
|
{ResourceId: 304, ResourceCode: "vip1badge", ResourceType: "badge", Name: "VIP1badge", Status: "active", ManagerGrantEnabled: false, MetadataJson: `{"badge_form":"tile"}`},
|
||||||
}}, nil
|
}}, nil
|
||||||
default:
|
default:
|
||||||
return &walletv1.ListResourcesResponse{}, nil
|
return &walletv1.ListResourcesResponse{}, nil
|
||||||
@ -178,9 +181,11 @@ func TestGenerateDiceRobotsUsesLikeiProfiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, resourceID := range append(wallets.grants, wallets.equips...) {
|
for _, resourceID := range append(wallets.grants, wallets.equips...) {
|
||||||
// 165 和 175 是资源库里的有效头像框,但创建机器人不能随机抽中,避免批量机器人展示这两款运营保留资源。
|
// 165/175 是资源库里的有效头像框但属于运营保留资源;176 超过机器人头像框价格上限;
|
||||||
if resourceID == 165 || resourceID == 175 {
|
// 301/302 是非 VIP 徽章。创建机器人随机池必须同时排除这些资源,避免新账号继续带出旧规则。
|
||||||
t.Fatalf("robot appearance must exclude avatar frame %d from random grants/equips", resourceID)
|
switch resourceID {
|
||||||
|
case 165, 175, 176, 301, 302:
|
||||||
|
t.Fatalf("robot appearance must exclude resource %d from random grants/equips", resourceID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -340,9 +340,9 @@ func (h *Handler) DebitCoinSellerStock(c *gin.Context) {
|
|||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeHostOrgAuditLog(c, h.audit, "coin-seller-stock-debit", "wallet_entries", sellerUserID,
|
writeHostOrgAuditLog(c, h.audit, "coin-seller-stock-debit", "coin_seller_stock_records", sellerUserID,
|
||||||
fmt.Sprintf("command_id=%s seller_user_id=%d coin_amount=%d transaction_id=%s reason=%q",
|
fmt.Sprintf("command_id=%s seller_user_id=%d stock_type=%s coin_amount=%d paid_currency_code=%s paid_amount_micro=%d transaction_id=%s reason=%q",
|
||||||
strings.TrimSpace(req.CommandID), sellerUserID, req.CoinAmount, result.GetTransactionId(), strings.TrimSpace(req.Reason)))
|
strings.TrimSpace(req.CommandID), sellerUserID, result.GetStockType(), result.GetCoinAmount(), result.GetPaidCurrencyCode(), result.GetPaidAmountMicro(), result.GetTransactionId(), strings.TrimSpace(req.Reason)))
|
||||||
response.Created(c, coinSellerStockDebitFromProto(sellerUserID, req.CoinAmount, result))
|
response.Created(c, coinSellerStockDebitFromProto(sellerUserID, req.CoinAmount, result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,11 +53,13 @@ type ManagerListItem struct {
|
|||||||
Contact string `json:"contact"`
|
Contact string `json:"contact"`
|
||||||
CanGrantAvatarFrame bool `json:"canGrantAvatarFrame"`
|
CanGrantAvatarFrame bool `json:"canGrantAvatarFrame"`
|
||||||
CanGrantVehicle bool `json:"canGrantVehicle"`
|
CanGrantVehicle bool `json:"canGrantVehicle"`
|
||||||
|
CanGrantBadge bool `json:"canGrantBadge"`
|
||||||
CanUpdateUserLevel bool `json:"canUpdateUserLevel"`
|
CanUpdateUserLevel bool `json:"canUpdateUserLevel"`
|
||||||
CanAddBDLeader bool `json:"canAddBdLeader"`
|
CanAddBDLeader bool `json:"canAddBdLeader"`
|
||||||
CanAddAdmin bool `json:"canAddAdmin"`
|
CanAddAdmin bool `json:"canAddAdmin"`
|
||||||
CanAddSuperadmin bool `json:"canAddSuperadmin"`
|
CanAddSuperadmin bool `json:"canAddSuperadmin"`
|
||||||
CanBlockUser bool `json:"canBlockUser"`
|
CanBlockUser bool `json:"canBlockUser"`
|
||||||
|
CanTransferCountry bool `json:"canTransferUserCountry"`
|
||||||
BDLeaderCount int64 `json:"bdLeaderCount"`
|
BDLeaderCount int64 `json:"bdLeaderCount"`
|
||||||
LastInvitedAtMs int64 `json:"lastInvitedAtMs"`
|
LastInvitedAtMs int64 `json:"lastInvitedAtMs"`
|
||||||
CreatedAtMs int64 `json:"createdAtMs"`
|
CreatedAtMs int64 `json:"createdAtMs"`
|
||||||
@ -600,24 +602,26 @@ func (r *Reader) CreateManagerProfile(ctx context.Context, userID int64, actorID
|
|||||||
// 不新增第二条身份,也不触碰该经理已经邀请出的 BD Leader/Admin 归属统计。
|
// 不新增第二条身份,也不触碰该经理已经邀请出的 BD Leader/Admin 归属统计。
|
||||||
if _, err := r.db.ExecContext(ctx, `
|
if _, err := r.db.ExecContext(ctx, `
|
||||||
INSERT INTO manager_profiles (
|
INSERT INTO manager_profiles (
|
||||||
app_code, user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle,
|
app_code, user_id, status, contact_info, can_grant_avatar_frame, can_grant_vehicle, can_grant_badge,
|
||||||
can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user,
|
can_update_user_level, can_add_bd_leader, can_add_admin, can_add_superadmin, can_block_user, can_transfer_user_country,
|
||||||
created_by_admin_id, created_at_ms, updated_at_ms
|
created_by_admin_id, created_at_ms, updated_at_ms
|
||||||
) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
status = 'active',
|
status = 'active',
|
||||||
contact_info = VALUES(contact_info),
|
contact_info = VALUES(contact_info),
|
||||||
can_grant_avatar_frame = VALUES(can_grant_avatar_frame),
|
can_grant_avatar_frame = VALUES(can_grant_avatar_frame),
|
||||||
can_grant_vehicle = VALUES(can_grant_vehicle),
|
can_grant_vehicle = VALUES(can_grant_vehicle),
|
||||||
|
can_grant_badge = VALUES(can_grant_badge),
|
||||||
can_update_user_level = VALUES(can_update_user_level),
|
can_update_user_level = VALUES(can_update_user_level),
|
||||||
can_add_bd_leader = VALUES(can_add_bd_leader),
|
can_add_bd_leader = VALUES(can_add_bd_leader),
|
||||||
can_add_admin = VALUES(can_add_admin),
|
can_add_admin = VALUES(can_add_admin),
|
||||||
can_add_superadmin = VALUES(can_add_superadmin),
|
can_add_superadmin = VALUES(can_add_superadmin),
|
||||||
can_block_user = VALUES(can_block_user),
|
can_block_user = VALUES(can_block_user),
|
||||||
|
can_transfer_user_country = VALUES(can_transfer_user_country),
|
||||||
updated_at_ms = VALUES(updated_at_ms)
|
updated_at_ms = VALUES(updated_at_ms)
|
||||||
`, appCode, userID, contact, permissions.CanGrantAvatarFrame, permissions.CanGrantVehicle,
|
`, appCode, userID, contact, permissions.CanGrantAvatarFrame, permissions.CanGrantVehicle,
|
||||||
permissions.CanUpdateUserLevel, permissions.CanAddBDLeader, permissions.CanAddAdmin,
|
permissions.CanGrantBadge, permissions.CanUpdateUserLevel, permissions.CanAddBDLeader, permissions.CanAddAdmin,
|
||||||
permissions.CanAddSuperadmin, permissions.CanBlockUser, actorID, nowMs, nowMs); err != nil {
|
permissions.CanAddSuperadmin, permissions.CanBlockUser, permissions.CanTransferCountry, actorID, nowMs, nowMs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
items, _, err := r.ListManagers(ctx, listQuery{Keyword: strconv.FormatInt(userID, 10), Page: 1, PageSize: 1})
|
items, _, err := r.ListManagers(ctx, listQuery{Keyword: strconv.FormatInt(userID, 10), Page: 1, PageSize: 1})
|
||||||
@ -716,15 +720,15 @@ func (r *Reader) ListManagers(ctx context.Context, query listQuery) ([]*ManagerL
|
|||||||
SELECT mp.user_id, COALESCE(manager.current_display_user_id, ''), COALESCE(manager.username, ''),
|
SELECT mp.user_id, COALESCE(manager.current_display_user_id, ''), COALESCE(manager.username, ''),
|
||||||
COALESCE(manager.avatar, ''), `+userCountryRegionIDSQL("manager_region")+`, `+userCountryRegionNameSQL("manager_region")+`,
|
COALESCE(manager.avatar, ''), `+userCountryRegionIDSQL("manager_region")+`, `+userCountryRegionNameSQL("manager_region")+`,
|
||||||
mp.status, COALESCE(mp.contact_info, ''),
|
mp.status, COALESCE(mp.contact_info, ''),
|
||||||
mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_update_user_level,
|
mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_grant_badge, mp.can_update_user_level,
|
||||||
mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user,
|
mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country,
|
||||||
COUNT(DISTINCT bl.user_id), COALESCE(MAX(bl.created_at_ms), 0),
|
COUNT(DISTINCT bl.user_id), COALESCE(MAX(bl.created_at_ms), 0),
|
||||||
mp.created_at_ms, mp.updated_at_ms
|
mp.created_at_ms, mp.updated_at_ms
|
||||||
%s
|
%s
|
||||||
GROUP BY mp.user_id, manager.current_display_user_id, manager.username, manager.avatar,
|
GROUP BY mp.user_id, manager.current_display_user_id, manager.username, manager.avatar,
|
||||||
manager_region.region_id, manager_region.name, mp.status, mp.contact_info,
|
manager_region.region_id, manager_region.name, mp.status, mp.contact_info,
|
||||||
mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_update_user_level,
|
mp.can_grant_avatar_frame, mp.can_grant_vehicle, mp.can_grant_badge, mp.can_update_user_level,
|
||||||
mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user,
|
mp.can_add_bd_leader, mp.can_add_admin, mp.can_add_superadmin, mp.can_block_user, mp.can_transfer_user_country,
|
||||||
mp.created_at_ms, mp.updated_at_ms
|
mp.created_at_ms, mp.updated_at_ms
|
||||||
ORDER BY mp.updated_at_ms DESC, mp.user_id DESC
|
ORDER BY mp.updated_at_ms DESC, mp.user_id DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
@ -748,11 +752,13 @@ func (r *Reader) ListManagers(ctx context.Context, query listQuery) ([]*ManagerL
|
|||||||
&item.Contact,
|
&item.Contact,
|
||||||
&item.CanGrantAvatarFrame,
|
&item.CanGrantAvatarFrame,
|
||||||
&item.CanGrantVehicle,
|
&item.CanGrantVehicle,
|
||||||
|
&item.CanGrantBadge,
|
||||||
&item.CanUpdateUserLevel,
|
&item.CanUpdateUserLevel,
|
||||||
&item.CanAddBDLeader,
|
&item.CanAddBDLeader,
|
||||||
&item.CanAddAdmin,
|
&item.CanAddAdmin,
|
||||||
&item.CanAddSuperadmin,
|
&item.CanAddSuperadmin,
|
||||||
&item.CanBlockUser,
|
&item.CanBlockUser,
|
||||||
|
&item.CanTransferCountry,
|
||||||
&item.BDLeaderCount,
|
&item.BDLeaderCount,
|
||||||
&item.LastInvitedAtMs,
|
&item.LastInvitedAtMs,
|
||||||
&item.CreatedAtMs,
|
&item.CreatedAtMs,
|
||||||
|
|||||||
@ -65,54 +65,64 @@ type createManagerRequest struct {
|
|||||||
TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"`
|
TargetUserID flexibleInt64 `json:"targetUserId" binding:"required"`
|
||||||
CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"`
|
CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"`
|
||||||
CanGrantVehicle *bool `json:"canGrantVehicle"`
|
CanGrantVehicle *bool `json:"canGrantVehicle"`
|
||||||
|
CanGrantBadge *bool `json:"canGrantBadge"`
|
||||||
CanUpdateUserLevel *bool `json:"canUpdateUserLevel"`
|
CanUpdateUserLevel *bool `json:"canUpdateUserLevel"`
|
||||||
CanAddBDLeader *bool `json:"canAddBdLeader"`
|
CanAddBDLeader *bool `json:"canAddBdLeader"`
|
||||||
CanAddAdmin *bool `json:"canAddAdmin"`
|
CanAddAdmin *bool `json:"canAddAdmin"`
|
||||||
CanAddSuperadmin *bool `json:"canAddSuperadmin"`
|
CanAddSuperadmin *bool `json:"canAddSuperadmin"`
|
||||||
CanBlockUser *bool `json:"canBlockUser"`
|
CanBlockUser *bool `json:"canBlockUser"`
|
||||||
|
CanTransferCountry *bool `json:"canTransferUserCountry"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type updateManagerRequest struct {
|
type updateManagerRequest struct {
|
||||||
Contact *string `json:"contact"`
|
Contact *string `json:"contact"`
|
||||||
CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"`
|
CanGrantAvatarFrame *bool `json:"canGrantAvatarFrame"`
|
||||||
CanGrantVehicle *bool `json:"canGrantVehicle"`
|
CanGrantVehicle *bool `json:"canGrantVehicle"`
|
||||||
|
CanGrantBadge *bool `json:"canGrantBadge"`
|
||||||
CanUpdateUserLevel *bool `json:"canUpdateUserLevel"`
|
CanUpdateUserLevel *bool `json:"canUpdateUserLevel"`
|
||||||
CanAddBDLeader *bool `json:"canAddBdLeader"`
|
CanAddBDLeader *bool `json:"canAddBdLeader"`
|
||||||
CanAddAdmin *bool `json:"canAddAdmin"`
|
CanAddAdmin *bool `json:"canAddAdmin"`
|
||||||
CanAddSuperadmin *bool `json:"canAddSuperadmin"`
|
CanAddSuperadmin *bool `json:"canAddSuperadmin"`
|
||||||
CanBlockUser *bool `json:"canBlockUser"`
|
CanBlockUser *bool `json:"canBlockUser"`
|
||||||
|
CanTransferCountry *bool `json:"canTransferUserCountry"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type managerPermissions struct {
|
type managerPermissions struct {
|
||||||
CanGrantAvatarFrame bool
|
CanGrantAvatarFrame bool
|
||||||
CanGrantVehicle bool
|
CanGrantVehicle bool
|
||||||
|
CanGrantBadge bool
|
||||||
CanUpdateUserLevel bool
|
CanUpdateUserLevel bool
|
||||||
CanAddBDLeader bool
|
CanAddBDLeader bool
|
||||||
CanAddAdmin bool
|
CanAddAdmin bool
|
||||||
CanAddSuperadmin bool
|
CanAddSuperadmin bool
|
||||||
CanBlockUser bool
|
CanBlockUser bool
|
||||||
|
CanTransferCountry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r createManagerRequest) permissionsWithDefault() managerPermissions {
|
func (r createManagerRequest) permissionsWithDefault() managerPermissions {
|
||||||
return managerPermissions{
|
return managerPermissions{
|
||||||
CanGrantAvatarFrame: boolDefaultTrue(r.CanGrantAvatarFrame),
|
CanGrantAvatarFrame: boolDefaultTrue(r.CanGrantAvatarFrame),
|
||||||
CanGrantVehicle: boolDefaultTrue(r.CanGrantVehicle),
|
CanGrantVehicle: boolDefaultTrue(r.CanGrantVehicle),
|
||||||
|
CanGrantBadge: boolDefaultTrue(r.CanGrantBadge),
|
||||||
CanUpdateUserLevel: boolDefaultTrue(r.CanUpdateUserLevel),
|
CanUpdateUserLevel: boolDefaultTrue(r.CanUpdateUserLevel),
|
||||||
CanAddBDLeader: boolDefaultTrue(r.CanAddBDLeader),
|
CanAddBDLeader: boolDefaultTrue(r.CanAddBDLeader),
|
||||||
CanAddAdmin: boolDefaultTrue(r.CanAddAdmin),
|
CanAddAdmin: boolDefaultTrue(r.CanAddAdmin),
|
||||||
CanAddSuperadmin: boolDefaultTrue(r.CanAddSuperadmin),
|
CanAddSuperadmin: boolDefaultTrue(r.CanAddSuperadmin),
|
||||||
CanBlockUser: boolDefaultTrue(r.CanBlockUser),
|
CanBlockUser: boolDefaultTrue(r.CanBlockUser),
|
||||||
|
CanTransferCountry: boolDefaultTrue(r.CanTransferCountry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r updateManagerRequest) appendPermissionAssignments(assignments *[]string, args *[]any) {
|
func (r updateManagerRequest) appendPermissionAssignments(assignments *[]string, args *[]any) {
|
||||||
appendBoolAssignment(assignments, args, "can_grant_avatar_frame", r.CanGrantAvatarFrame)
|
appendBoolAssignment(assignments, args, "can_grant_avatar_frame", r.CanGrantAvatarFrame)
|
||||||
appendBoolAssignment(assignments, args, "can_grant_vehicle", r.CanGrantVehicle)
|
appendBoolAssignment(assignments, args, "can_grant_vehicle", r.CanGrantVehicle)
|
||||||
|
appendBoolAssignment(assignments, args, "can_grant_badge", r.CanGrantBadge)
|
||||||
appendBoolAssignment(assignments, args, "can_update_user_level", r.CanUpdateUserLevel)
|
appendBoolAssignment(assignments, args, "can_update_user_level", r.CanUpdateUserLevel)
|
||||||
appendBoolAssignment(assignments, args, "can_add_bd_leader", r.CanAddBDLeader)
|
appendBoolAssignment(assignments, args, "can_add_bd_leader", r.CanAddBDLeader)
|
||||||
appendBoolAssignment(assignments, args, "can_add_admin", r.CanAddAdmin)
|
appendBoolAssignment(assignments, args, "can_add_admin", r.CanAddAdmin)
|
||||||
appendBoolAssignment(assignments, args, "can_add_superadmin", r.CanAddSuperadmin)
|
appendBoolAssignment(assignments, args, "can_add_superadmin", r.CanAddSuperadmin)
|
||||||
appendBoolAssignment(assignments, args, "can_block_user", r.CanBlockUser)
|
appendBoolAssignment(assignments, args, "can_block_user", r.CanBlockUser)
|
||||||
|
appendBoolAssignment(assignments, args, "can_transfer_user_country", r.CanTransferCountry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendBoolAssignment(assignments *[]string, args *[]any, column string, value *bool) {
|
func appendBoolAssignment(assignments *[]string, args *[]any, column string, value *bool) {
|
||||||
@ -171,10 +181,11 @@ type coinSellerStockCreditRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coinSellerStockDebitRequest struct {
|
type coinSellerStockDebitRequest struct {
|
||||||
CommandID string `json:"commandId" binding:"required"`
|
CommandID string `json:"commandId" binding:"required"`
|
||||||
CoinAmount int64 `json:"coinAmount" binding:"required"`
|
CoinAmount int64 `json:"coinAmount" binding:"required"`
|
||||||
EvidenceRef string `json:"evidenceRef"`
|
RechargeAmount string `json:"rechargeAmount" binding:"required"`
|
||||||
Reason string `json:"reason" binding:"required"`
|
EvidenceRef string `json:"evidenceRef"`
|
||||||
|
Reason string `json:"reason" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type replaceCoinSellerSalaryRatesRequest struct {
|
type replaceCoinSellerSalaryRatesRequest struct {
|
||||||
|
|||||||
@ -15,11 +15,16 @@ type coinSellerStockCreditResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coinSellerStockDebitResponse struct {
|
type coinSellerStockDebitResponse struct {
|
||||||
TransactionID string `json:"transactionId"`
|
TransactionID string `json:"transactionId"`
|
||||||
SellerUserID int64 `json:"sellerUserId,string"`
|
SellerUserID int64 `json:"sellerUserId,string"`
|
||||||
CoinAmount int64 `json:"coinAmount"`
|
StockType string `json:"stockType"`
|
||||||
AvailableDelta int64 `json:"availableDelta"`
|
CoinAmount int64 `json:"coinAmount"`
|
||||||
BalanceAfter int64 `json:"balanceAfter"`
|
AvailableDelta int64 `json:"availableDelta"`
|
||||||
|
PaidCurrencyCode string `json:"paidCurrencyCode"`
|
||||||
|
PaidAmountMicro int64 `json:"paidAmountMicro"`
|
||||||
|
CountsAsSellerRecharge bool `json:"countsAsSellerRecharge"`
|
||||||
|
BalanceAfter int64 `json:"balanceAfter"`
|
||||||
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockCreditResponse {
|
func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockCreditResponse {
|
||||||
@ -39,19 +44,20 @@ func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSellerStockDebitFromProto(sellerUserID int64, coinAmount int64, item *walletv1.AdminCreditAssetResponse) coinSellerStockDebitResponse {
|
func coinSellerStockDebitFromProto(sellerUserID int64, coinAmount int64, item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockDebitResponse {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return coinSellerStockDebitResponse{SellerUserID: sellerUserID, CoinAmount: coinAmount, AvailableDelta: -coinAmount}
|
return coinSellerStockDebitResponse{SellerUserID: sellerUserID, CoinAmount: coinAmount, AvailableDelta: -coinAmount}
|
||||||
}
|
}
|
||||||
balanceAfter := int64(0)
|
|
||||||
if item.GetBalance() != nil {
|
|
||||||
balanceAfter = item.GetBalance().GetAvailableAmount()
|
|
||||||
}
|
|
||||||
return coinSellerStockDebitResponse{
|
return coinSellerStockDebitResponse{
|
||||||
TransactionID: item.GetTransactionId(),
|
TransactionID: item.GetTransactionId(),
|
||||||
SellerUserID: sellerUserID,
|
SellerUserID: sellerUserID,
|
||||||
CoinAmount: coinAmount,
|
StockType: item.GetStockType(),
|
||||||
AvailableDelta: -coinAmount,
|
CoinAmount: item.GetCoinAmount(),
|
||||||
BalanceAfter: balanceAfter,
|
AvailableDelta: item.GetCoinAmount(),
|
||||||
|
PaidCurrencyCode: item.GetPaidCurrencyCode(),
|
||||||
|
PaidAmountMicro: item.GetPaidAmountMicro(),
|
||||||
|
CountsAsSellerRecharge: item.GetCountsAsSellerRecharge(),
|
||||||
|
BalanceAfter: item.GetBalanceAfter(),
|
||||||
|
CreatedAtMS: item.GetCreatedAtMs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
coinSellerStatusActive = "active"
|
|
||||||
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
||||||
coinSellerStockTypePurchase = "usdt_purchase"
|
coinSellerStockTypePurchase = "usdt_purchase"
|
||||||
|
coinSellerStockTypeDeduction = "usdt_deduction"
|
||||||
coinSellerStockTypeCompensate = "coin_compensation"
|
coinSellerStockTypeCompensate = "coin_compensation"
|
||||||
paidCurrencyUSDT = "USDT"
|
paidCurrencyUSDT = "USDT"
|
||||||
sortByDiamond = "diamond"
|
sortByDiamond = "diamond"
|
||||||
@ -312,7 +312,7 @@ func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sell
|
|||||||
return nil, fmt.Errorf("stock type is invalid")
|
return nil, fmt.Errorf("stock type is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
seller, err := s.activeCoinSellerUser(ctx, sellerUserID, requestID)
|
seller, err := s.coinSellerUser(ctx, sellerUserID, requestID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -334,7 +334,7 @@ func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sell
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, sellerUserID int64, requestID string, req coinSellerStockDebitRequest) (*walletv1.AdminCreditAssetResponse, error) {
|
func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, sellerUserID int64, requestID string, req coinSellerStockDebitRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) {
|
||||||
if s.walletClient == nil {
|
if s.walletClient == nil {
|
||||||
return nil, fmt.Errorf("wallet client is not configured")
|
return nil, fmt.Errorf("wallet client is not configured")
|
||||||
}
|
}
|
||||||
@ -353,24 +353,33 @@ func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, selle
|
|||||||
if len(reason) > 512 || len(commandID) > 128 || len(evidenceRef) > 256 {
|
if len(reason) > 512 || len(commandID) > 128 || len(evidenceRef) > 256 {
|
||||||
return nil, fmt.Errorf("coin seller debit text fields are too long")
|
return nil, fmt.Errorf("coin seller debit text fields are too long")
|
||||||
}
|
}
|
||||||
if _, err := s.activeCoinSellerUser(ctx, sellerUserID, requestID); err != nil {
|
paidAmountMicro, err := parseUSDTAmountMicro(req.RechargeAmount)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// 币商扣除是运营对币商专用库存的负向调账:只扣 COIN_SELLER_COIN,不写普通 COIN,也不写进货记录。
|
seller, err := s.coinSellerUser(ctx, sellerUserID, requestID)
|
||||||
// wallet-service 的 AdminCreditAsset 负责余额不足校验、幂等冲突检测、wallet_entries 和 outbox 原子落账。
|
if err != nil {
|
||||||
return s.walletClient.AdminCreditAsset(ctx, &walletv1.AdminCreditAssetRequest{
|
return nil, err
|
||||||
CommandId: commandID,
|
}
|
||||||
TargetUserId: sellerUserID,
|
// 币商扣除按 USDT 进货冲回处理:钱包事务会同时写负向库存分录、负向进货记录和统计 outbox,
|
||||||
AssetType: coinSellerMerchantAssetType,
|
// 这样后台列表、币商流水和充值聚合都使用同一条账务事实,避免只扣余额却不冲收入。
|
||||||
Amount: -req.CoinAmount,
|
return s.walletClient.AdminCreditCoinSellerStock(ctx, &walletv1.AdminCreditCoinSellerStockRequest{
|
||||||
OperatorUserId: actorID,
|
CommandId: commandID,
|
||||||
Reason: reason,
|
SellerUserId: sellerUserID,
|
||||||
EvidenceRef: evidenceRef,
|
SellerCountryId: seller.CountryID,
|
||||||
AppCode: appctx.FromContext(ctx),
|
SellerRegionId: seller.RegionID,
|
||||||
|
StockType: coinSellerStockTypeDeduction,
|
||||||
|
CoinAmount: req.CoinAmount,
|
||||||
|
PaidCurrencyCode: paidCurrencyUSDT,
|
||||||
|
PaidAmountMicro: paidAmountMicro,
|
||||||
|
EvidenceRef: evidenceRef,
|
||||||
|
OperatorUserId: actorID,
|
||||||
|
Reason: reason,
|
||||||
|
AppCode: appctx.FromContext(ctx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) activeCoinSellerUser(ctx context.Context, sellerUserID int64, requestID string) (*userclient.User, error) {
|
func (s *Service) coinSellerUser(ctx context.Context, sellerUserID int64, requestID string) (*userclient.User, error) {
|
||||||
if s == nil || s.userClient == nil {
|
if s == nil || s.userClient == nil {
|
||||||
return nil, fmt.Errorf("user client is not configured")
|
return nil, fmt.Errorf("user client is not configured")
|
||||||
}
|
}
|
||||||
@ -382,8 +391,8 @@ func (s *Service) activeCoinSellerUser(ctx context.Context, sellerUserID int64,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if profile == nil || profile.Status != coinSellerStatusActive || profile.MerchantAssetType != coinSellerMerchantAssetType {
|
if profile == nil || profile.MerchantAssetType != coinSellerMerchantAssetType {
|
||||||
return nil, fmt.Errorf("target user is not an active coin seller")
|
return nil, fmt.Errorf("target user is not a coin seller")
|
||||||
}
|
}
|
||||||
seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
|
seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
|
|||||||
@ -237,37 +237,12 @@ func (h *Handler) resolveUserID(ctx context.Context, keyword string) (int64, boo
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
// 邀请奖励汇总页只把内部 user_id 交给 activity-service;展示 ID 的兼容规则集中在 shared 解析器里。
|
||||||
return 0, false, true
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
}
|
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillUsers(ctx context.Context, claims []claimDTO) error {
|
func (h *Handler) fillUsers(ctx context.Context, claims []claimDTO) error {
|
||||||
|
|||||||
@ -96,6 +96,31 @@ type thirdPartyPaymentMethodDTO struct {
|
|||||||
UpdatedAtMS int64 `json:"updatedAtMs"`
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type temporaryPaymentLinkDTO struct {
|
||||||
|
OrderID string `json:"orderId"`
|
||||||
|
AppCode string `json:"appCode"`
|
||||||
|
CommandID string `json:"commandId,omitempty"`
|
||||||
|
AudienceType string `json:"audienceType"`
|
||||||
|
ProductName string `json:"productName"`
|
||||||
|
CoinAmount int64 `json:"coinAmount"`
|
||||||
|
USDMinorAmount int64 `json:"usdMinorAmount"`
|
||||||
|
ProviderCode string `json:"providerCode"`
|
||||||
|
PaymentMethodID int64 `json:"paymentMethodId"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
CurrencyCode string `json:"currencyCode"`
|
||||||
|
ProviderAmountMinor int64 `json:"providerAmountMinor"`
|
||||||
|
PayWay string `json:"payWay"`
|
||||||
|
PayType string `json:"payType"`
|
||||||
|
PayURL string `json:"payUrl"`
|
||||||
|
ProviderOrderID string `json:"providerOrderId"`
|
||||||
|
TxHash string `json:"txHash"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
FailureReason string `json:"failureReason"`
|
||||||
|
TransactionID string `json:"transactionId"`
|
||||||
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
|
}
|
||||||
|
|
||||||
func rechargeBillFromProto(item *walletv1.RechargeBill) rechargeBillDTO {
|
func rechargeBillFromProto(item *walletv1.RechargeBill) rechargeBillDTO {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return rechargeBillDTO{}
|
return rechargeBillDTO{}
|
||||||
@ -247,6 +272,35 @@ func thirdPartyPaymentMethodFromProto(item *walletv1.ThirdPartyPaymentMethod) th
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func temporaryPaymentLinkFromProto(item *walletv1.ExternalRechargeOrder) temporaryPaymentLinkDTO {
|
||||||
|
if item == nil {
|
||||||
|
return temporaryPaymentLinkDTO{}
|
||||||
|
}
|
||||||
|
return temporaryPaymentLinkDTO{
|
||||||
|
OrderID: item.GetOrderId(),
|
||||||
|
AppCode: item.GetAppCode(),
|
||||||
|
AudienceType: item.GetAudienceType(),
|
||||||
|
ProductName: item.GetProductName(),
|
||||||
|
CoinAmount: item.GetCoinAmount(),
|
||||||
|
USDMinorAmount: item.GetUsdMinorAmount(),
|
||||||
|
ProviderCode: item.GetProviderCode(),
|
||||||
|
PaymentMethodID: item.GetPaymentMethodId(),
|
||||||
|
CountryCode: item.GetCountryCode(),
|
||||||
|
CurrencyCode: item.GetCurrencyCode(),
|
||||||
|
ProviderAmountMinor: item.GetProviderAmountMinor(),
|
||||||
|
PayWay: item.GetPayWay(),
|
||||||
|
PayType: item.GetPayType(),
|
||||||
|
PayURL: item.GetPayUrl(),
|
||||||
|
ProviderOrderID: item.GetProviderOrderId(),
|
||||||
|
TxHash: item.GetTxHash(),
|
||||||
|
Status: item.GetStatus(),
|
||||||
|
FailureReason: item.GetFailureReason(),
|
||||||
|
TransactionID: item.GetTransactionId(),
|
||||||
|
CreatedAtMS: item.GetCreatedAtMs(),
|
||||||
|
UpdatedAtMS: item.GetUpdatedAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func formatUSDTMicro(amount int64) string {
|
func formatUSDTMicro(amount int64) string {
|
||||||
if amount <= 0 {
|
if amount <= 0 {
|
||||||
return "0"
|
return "0"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"hyapp-admin-server/internal/appctx"
|
"hyapp-admin-server/internal/appctx"
|
||||||
"hyapp-admin-server/internal/integration/walletclient"
|
"hyapp-admin-server/internal/integration/walletclient"
|
||||||
@ -37,11 +38,19 @@ func New(wallet walletclient.Client, userDB *sql.DB, audit shared.OperationLogge
|
|||||||
func (h *Handler) ListRechargeBills(c *gin.Context) {
|
func (h *Handler) ListRechargeBills(c *gin.Context) {
|
||||||
options := shared.ListOptions(c)
|
options := shared.ListOptions(c)
|
||||||
appCode := appctx.FromContext(c.Request.Context())
|
appCode := appctx.FromContext(c.Request.Context())
|
||||||
|
userID, ok := h.resolveRechargeBillUserFilter(c, appCode, "user_id 参数不正确", "user_id", "userId")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sellerUserID, ok := h.resolveRechargeBillUserFilter(c, appCode, "seller_user_id 参数不正确", "seller_user_id", "sellerUserId")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
resp, err := h.wallet.ListRechargeBills(c.Request.Context(), &walletv1.ListRechargeBillsRequest{
|
resp, err := h.wallet.ListRechargeBills(c.Request.Context(), &walletv1.ListRechargeBillsRequest{
|
||||||
RequestId: middleware.CurrentRequestID(c),
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
UserId: queryInt64(c, "user_id", "userId"),
|
UserId: userID,
|
||||||
SellerUserId: queryInt64(c, "seller_user_id", "sellerUserId"),
|
SellerUserId: sellerUserID,
|
||||||
RegionId: queryInt64(c, "region_id", "regionId"),
|
RegionId: queryInt64(c, "region_id", "regionId"),
|
||||||
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
|
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
|
||||||
Status: options.Status,
|
Status: options.Status,
|
||||||
@ -68,6 +77,24 @@ func (h *Handler) ListRechargeBills(c *gin.Context) {
|
|||||||
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
|
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) resolveRechargeBillUserFilter(c *gin.Context, appCode string, invalidMessage string, keys ...string) (int64, bool) {
|
||||||
|
keyword := strings.TrimSpace(firstQuery(c, keys...))
|
||||||
|
if keyword == "" {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
// 充值账单事实在 wallet-service,筛选字段只能传内部 user_id;后台先把长 ID、默认短号、当前展示号和 active 靓号解析成同一个稳定 ID。
|
||||||
|
userID, _, err := shared.ResolveExactUserIDOrNumericFallback(c.Request.Context(), h.userDB, appCode, keyword, time.Now().UnixMilli())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shared.ErrInvalidUserIdentityFilter) {
|
||||||
|
response.BadRequest(c, invalidMessage)
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
response.ServerError(c, "查询账单用户信息失败")
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return userID, true
|
||||||
|
}
|
||||||
|
|
||||||
// fillRechargeBillUsers 将用户资料写回当前页账单 DTO;缺失资料会在 DTO 层保留长 ID,避免影响账单事实展示。
|
// fillRechargeBillUsers 将用户资料写回当前页账单 DTO;缺失资料会在 DTO 层保留长 ID,避免影响账单事实展示。
|
||||||
func (h *Handler) fillRechargeBillUsers(ctx context.Context, appCode string, items []rechargeBillDTO) error {
|
func (h *Handler) fillRechargeBillUsers(ctx context.Context, appCode string, items []rechargeBillDTO) error {
|
||||||
userIDs := collectRechargeBillUserIDs(items)
|
userIDs := collectRechargeBillUserIDs(items)
|
||||||
@ -261,6 +288,47 @@ func (h *Handler) ListThirdPartyPaymentChannels(c *gin.Context) {
|
|||||||
response.OK(c, gin.H{"items": items, "total": len(items)})
|
response.OK(c, gin.H{"items": items, "total": len(items)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ListTemporaryPaymentLinks(c *gin.Context) {
|
||||||
|
options := shared.ListOptions(c)
|
||||||
|
resp, err := h.wallet.ListTemporaryRechargeOrders(c.Request.Context(), &walletv1.ListTemporaryRechargeOrdersRequest{
|
||||||
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
|
Status: options.Status,
|
||||||
|
ProviderCode: strings.TrimSpace(firstQuery(c, "provider_code", "providerCode")),
|
||||||
|
Keyword: options.Keyword,
|
||||||
|
Page: int32(options.Page),
|
||||||
|
PageSize: int32(options.PageSize),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
writeWalletError(c, err, "获取三方临时支付链接失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items := make([]temporaryPaymentLinkDTO, 0, len(resp.GetOrders()))
|
||||||
|
for _, item := range resp.GetOrders() {
|
||||||
|
items = append(items, temporaryPaymentLinkFromProto(item))
|
||||||
|
}
|
||||||
|
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetTemporaryPaymentLink(c *gin.Context) {
|
||||||
|
orderID := strings.TrimSpace(c.Param("order_id"))
|
||||||
|
if orderID == "" {
|
||||||
|
response.BadRequest(c, "支付链接订单号不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 查询单条订单会进入 wallet-service 的支付状态刷新逻辑;admin 页面只发起核验,不在浏览器里拼三方查询参数。
|
||||||
|
resp, err := h.wallet.GetTemporaryRechargeOrder(c.Request.Context(), &walletv1.GetTemporaryRechargeOrderRequest{
|
||||||
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
|
OrderId: orderID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
writeWalletError(c, err, "校验三方临时支付链接失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, temporaryPaymentLinkFromProto(resp.GetOrder()))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) SetThirdPartyPaymentMethodStatus(c *gin.Context) {
|
func (h *Handler) SetThirdPartyPaymentMethodStatus(c *gin.Context) {
|
||||||
methodID := queryPathInt64(c, "method_id")
|
methodID := queryPathInt64(c, "method_id")
|
||||||
if methodID <= 0 {
|
if methodID <= 0 {
|
||||||
@ -316,6 +384,14 @@ func (h *Handler) UpdateThirdPartyPaymentRate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
||||||
|
var request thirdPartyPaymentRateSyncRequest
|
||||||
|
if c.Request.Body != nil && c.Request.ContentLength != 0 {
|
||||||
|
// 同步接口历史上允许空 body 直接触发;现在弹窗只新增上浮比例,空 body 仍按 0% 处理,避免旧后台或脚本调用被破坏。
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil || !validThirdPartyPaymentRateMarkup(request.MarkupPercent) {
|
||||||
|
response.BadRequest(c, "支付汇率同步参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
resp, err := h.wallet.ListThirdPartyPaymentChannels(c.Request.Context(), &walletv1.ListThirdPartyPaymentChannelsRequest{
|
resp, err := h.wallet.ListThirdPartyPaymentChannels(c.Request.Context(), &walletv1.ListThirdPartyPaymentChannelsRequest{
|
||||||
RequestId: middleware.CurrentRequestID(c),
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
AppCode: appctx.FromContext(c.Request.Context()),
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
@ -332,7 +408,7 @@ func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
|||||||
response.OK(c, thirdPartyPaymentRateSyncDTO{UpdatedCount: 0})
|
response.OK(c, thirdPartyPaymentRateSyncDTO{UpdatedCount: 0})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 汇率同步必须以服务端实时拉取结果为准:前端按钮只触发动作,不传任何汇率,避免浏览器篡改本币金额。
|
// 汇率同步必须以服务端实时拉取结果为准:前端只传运营确认的上浮比例,原始汇率、失败切换和最终写库都留在服务端收敛。
|
||||||
rateClient := h.exchangeRates
|
rateClient := h.exchangeRates
|
||||||
if rateClient == nil {
|
if rateClient == nil {
|
||||||
rateClient = newDefaultExchangeRateClient()
|
rateClient = newDefaultExchangeRateClient()
|
||||||
@ -344,10 +420,11 @@ func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
updated := 0
|
updated := 0
|
||||||
skipped := make(map[string]struct{})
|
skipped := make(map[string]struct{})
|
||||||
|
writtenRates := make(map[string]string, len(rateResult.Rates))
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
currencyCode := strings.ToUpper(strings.TrimSpace(method.GetCurrencyCode()))
|
currencyCode := strings.ToUpper(strings.TrimSpace(method.GetCurrencyCode()))
|
||||||
rate := rateResult.Rates[currencyCode]
|
rate, ok := applyThirdPartyPaymentRateMarkup(rateResult.Rates[currencyCode], request.MarkupPercent)
|
||||||
if rate == "" {
|
if !ok {
|
||||||
skipped[currencyCode] = struct{}{}
|
skipped[currencyCode] = struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -361,6 +438,7 @@ func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
|||||||
writeWalletError(c, err, "写入支付汇率失败")
|
writeWalletError(c, err, "写入支付汇率失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writtenRates[currencyCode] = rate
|
||||||
updated++
|
updated++
|
||||||
}
|
}
|
||||||
skippedCurrencies := sortedMapKeys(skipped)
|
skippedCurrencies := sortedMapKeys(skipped)
|
||||||
@ -371,7 +449,7 @@ func (h *Handler) SyncThirdPartyPaymentRates(c *gin.Context) {
|
|||||||
MissingCurrencies: skippedCurrencies,
|
MissingCurrencies: skippedCurrencies,
|
||||||
SourceNames: rateResult.SourceNames,
|
SourceNames: rateResult.SourceNames,
|
||||||
Sources: rateResult.Sources,
|
Sources: rateResult.Sources,
|
||||||
Rates: rateResult.Rates,
|
Rates: writtenRates,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +473,10 @@ type thirdPartyPaymentRateRequest struct {
|
|||||||
USDToCurrencyRate string `json:"usdToCurrencyRate"`
|
USDToCurrencyRate string `json:"usdToCurrencyRate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type thirdPartyPaymentRateSyncRequest struct {
|
||||||
|
MarkupPercent float64 `json:"markupPercent"`
|
||||||
|
}
|
||||||
|
|
||||||
func queryInt64(c *gin.Context, keys ...string) int64 {
|
func queryInt64(c *gin.Context, keys ...string) int64 {
|
||||||
value := strings.TrimSpace(firstQuery(c, keys...))
|
value := strings.TrimSpace(firstQuery(c, keys...))
|
||||||
if value == "" {
|
if value == "" {
|
||||||
@ -491,6 +573,27 @@ func allDigits(value string) bool {
|
|||||||
return value != ""
|
return value != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validThirdPartyPaymentRateMarkup(markupPercent float64) bool {
|
||||||
|
// 上浮比例来自后台运营弹窗,只允许 0-100 的有限数值;负数会压低支付汇率,过大数值会直接放大用户本币支付金额。
|
||||||
|
return !math.IsNaN(markupPercent) && !math.IsInf(markupPercent, 0) && markupPercent >= 0 && markupPercent <= 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyThirdPartyPaymentRateMarkup(rateText string, markupPercent float64) (string, bool) {
|
||||||
|
rawRate, err := strconv.ParseFloat(strings.TrimSpace(rateText), 64)
|
||||||
|
if err != nil || rawRate <= 0 || !validThirdPartyPaymentRateMarkup(markupPercent) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
// 汇率源返回的是实时 USD->本币原值;写库前统一叠加运营设置的上浮比例,再按 1 位小数向上取,保证 H5 下单看到的支付汇率不低于实时汇率。
|
||||||
|
markedRate := rawRate * (1 + markupPercent/100)
|
||||||
|
roundedRate := math.Ceil(markedRate*10-1e-9) / 10
|
||||||
|
return trimOneDecimalRate(strconv.FormatFloat(roundedRate, 'f', 1, 64)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimOneDecimalRate(rateText string) string {
|
||||||
|
// 产品要求“最多 1 位小数”,整数汇率不保留无意义的 .0,避免后台列表和人工核对时出现多余精度。
|
||||||
|
return strings.TrimSuffix(rateText, ".0")
|
||||||
|
}
|
||||||
|
|
||||||
func writeWalletError(c *gin.Context, err error, fallback string) {
|
func writeWalletError(c *gin.Context, err error, fallback string) {
|
||||||
message := fallback
|
message := fallback
|
||||||
if st, ok := status.FromError(err); ok && strings.TrimSpace(st.Message()) != "" {
|
if st, ok := status.FromError(err); ok && strings.TrimSpace(st.Message()) != "" {
|
||||||
|
|||||||
@ -13,9 +13,42 @@ import (
|
|||||||
"hyapp-admin-server/internal/middleware"
|
"hyapp-admin-server/internal/middleware"
|
||||||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestListRechargeBillsResolvesDisplayIDsBeforeWalletFilter(t *testing.T) {
|
||||||
|
db, sqlMock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create sql mock failed: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
wallet := &mockPaymentWallet{rechargeBillsResp: &walletv1.ListRechargeBillsResponse{}}
|
||||||
|
router := newPaymentHandlerTestRouter(New(wallet, db, nil))
|
||||||
|
userID := int64(327702592329093120)
|
||||||
|
sellerUserID := int64(328453424662192128)
|
||||||
|
expectUserIdentityLookup(sqlMock, "111", userID)
|
||||||
|
expectUserIdentityLookup(sqlMock, "167864", sellerUserID)
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/admin/payment/recharge-bills?user_id=111&seller_user_id=167864", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if wallet.lastRechargeBills == nil ||
|
||||||
|
wallet.lastRechargeBills.GetUserId() != userID ||
|
||||||
|
wallet.lastRechargeBills.GetSellerUserId() != sellerUserID {
|
||||||
|
t.Fatalf("recharge bill user filters should be resolved before wallet call: %+v", wallet.lastRechargeBills)
|
||||||
|
}
|
||||||
|
if err := sqlMock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Fatalf("sql expectations mismatch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.T) {
|
func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.T) {
|
||||||
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
|
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
|
||||||
Channels: []*walletv1.ThirdPartyPaymentChannel{{
|
Channels: []*walletv1.ThirdPartyPaymentChannel{{
|
||||||
@ -90,6 +123,91 @@ func TestListThirdPartyPaymentChannelsForwardsIncludeDisabledMethods(t *testing.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListTemporaryPaymentLinksForwardsFilters(t *testing.T) {
|
||||||
|
wallet := &mockPaymentWallet{temporaryOrdersResp: &walletv1.ListTemporaryRechargeOrdersResponse{
|
||||||
|
Total: 1,
|
||||||
|
Orders: []*walletv1.ExternalRechargeOrder{{
|
||||||
|
OrderId: "tmp_1001",
|
||||||
|
AppCode: "lalu",
|
||||||
|
AudienceType: "temporary",
|
||||||
|
ProductName: "Temporary payment USD 1.99",
|
||||||
|
CoinAmount: 0,
|
||||||
|
UsdMinorAmount: 199,
|
||||||
|
ProviderCode: "mifapay",
|
||||||
|
PaymentMethodId: 810,
|
||||||
|
CountryCode: "SA",
|
||||||
|
CurrencyCode: "SAR",
|
||||||
|
ProviderAmountMinor: 746,
|
||||||
|
PayWay: "Card",
|
||||||
|
PayType: "MADA",
|
||||||
|
PayUrl: "https://pay.example/tmp_1001",
|
||||||
|
ProviderOrderId: "mifa_1001",
|
||||||
|
Status: "redirected",
|
||||||
|
CreatedAtMs: 1700000000000,
|
||||||
|
UpdatedAtMs: 1700000000001,
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/admin/payment/temporary-links?status=redirected&provider_code=mifapay&keyword=tmp&page=2&page_size=30", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if wallet.lastTemporaryOrders == nil ||
|
||||||
|
wallet.lastTemporaryOrders.GetAppCode() != "lalu" ||
|
||||||
|
wallet.lastTemporaryOrders.GetStatus() != "redirected" ||
|
||||||
|
wallet.lastTemporaryOrders.GetProviderCode() != "mifapay" ||
|
||||||
|
wallet.lastTemporaryOrders.GetKeyword() != "tmp" ||
|
||||||
|
wallet.lastTemporaryOrders.GetPage() != 2 ||
|
||||||
|
wallet.lastTemporaryOrders.GetPageSize() != 30 {
|
||||||
|
t.Fatalf("temporary link list request mismatch: %+v", wallet.lastTemporaryOrders)
|
||||||
|
}
|
||||||
|
var response adminPaymentTestResponse
|
||||||
|
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
|
||||||
|
t.Fatalf("decode response failed: %v", err)
|
||||||
|
}
|
||||||
|
items := response.Data["items"].([]any)
|
||||||
|
item := items[0].(map[string]any)
|
||||||
|
if response.Code != 0 || response.Data["total"].(float64) != 1 || item["orderId"] != "tmp_1001" || item["payUrl"] != "https://pay.example/tmp_1001" || item["status"] != "redirected" {
|
||||||
|
t.Fatalf("temporary link response mismatch: %+v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTemporaryPaymentLinkRefreshesSingleOrder(t *testing.T) {
|
||||||
|
wallet := &mockPaymentWallet{temporaryOrderResp: &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{
|
||||||
|
OrderId: "tmp_1001",
|
||||||
|
AppCode: "lalu",
|
||||||
|
AudienceType: "temporary",
|
||||||
|
ProviderCode: "v5pay",
|
||||||
|
PayUrl: "https://pay.example/tmp_1001",
|
||||||
|
Status: "paid",
|
||||||
|
}}}
|
||||||
|
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/admin/payment/temporary-links/tmp_1001", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if wallet.lastTemporaryOrder == nil ||
|
||||||
|
wallet.lastTemporaryOrder.GetAppCode() != "lalu" ||
|
||||||
|
wallet.lastTemporaryOrder.GetOrderId() != "tmp_1001" {
|
||||||
|
t.Fatalf("temporary link get request mismatch: %+v", wallet.lastTemporaryOrder)
|
||||||
|
}
|
||||||
|
var response adminPaymentTestResponse
|
||||||
|
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
|
||||||
|
t.Fatalf("decode response failed: %v", err)
|
||||||
|
}
|
||||||
|
if response.Code != 0 || response.Data["orderId"] != "tmp_1001" || response.Data["status"] != "paid" {
|
||||||
|
t.Fatalf("temporary link get response mismatch: %+v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetThirdPartyPaymentMethodStatusForwardsOperator(t *testing.T) {
|
func TestSetThirdPartyPaymentMethodStatusForwardsOperator(t *testing.T) {
|
||||||
wallet := &mockPaymentWallet{}
|
wallet := &mockPaymentWallet{}
|
||||||
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
||||||
@ -130,7 +248,7 @@ func TestUpdateThirdPartyPaymentRateTrimsRateAndForwardsOperator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSyncThirdPartyPaymentRatesUpdatesAllMethodsFromFetchedCurrencyRates(t *testing.T) {
|
func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilsToOneDecimal(t *testing.T) {
|
||||||
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
|
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
|
||||||
Channels: []*walletv1.ThirdPartyPaymentChannel{{
|
Channels: []*walletv1.ThirdPartyPaymentChannel{{
|
||||||
AppCode: "lalu",
|
AppCode: "lalu",
|
||||||
@ -157,7 +275,8 @@ func TestSyncThirdPartyPaymentRatesUpdatesAllMethodsFromFetchedCurrencyRates(t *
|
|||||||
SourceNames: []string{"open.er-api.com"},
|
SourceNames: []string{"open.er-api.com"},
|
||||||
}}
|
}}
|
||||||
router := newPaymentHandlerTestRouter(handler)
|
router := newPaymentHandlerTestRouter(handler)
|
||||||
request := httptest.NewRequest(http.MethodPost, "/admin/payment/third-party-rates/sync", nil)
|
request := httptest.NewRequest(http.MethodPost, "/admin/payment/third-party-rates/sync", bytes.NewBufferString(`{"markupPercent":3}`))
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
router.ServeHTTP(recorder, request)
|
router.ServeHTTP(recorder, request)
|
||||||
@ -178,7 +297,7 @@ func TestSyncThirdPartyPaymentRatesUpdatesAllMethodsFromFetchedCurrencyRates(t *
|
|||||||
}
|
}
|
||||||
got[req.GetMethodId()] = req.GetUsdToCurrencyRate()
|
got[req.GetMethodId()] = req.GetUsdToCurrencyRate()
|
||||||
}
|
}
|
||||||
if got[810] != "3.75000000" || got[811] != "0.37600000" || got[2310] != "3.75000000" {
|
if got[810] != "3.9" || got[811] != "0.4" || got[2310] != "3.9" {
|
||||||
t.Fatalf("synced rates mismatch: %+v", got)
|
t.Fatalf("synced rates mismatch: %+v", got)
|
||||||
}
|
}
|
||||||
var response adminPaymentTestResponse
|
var response adminPaymentTestResponse
|
||||||
@ -190,6 +309,23 @@ func TestSyncThirdPartyPaymentRatesUpdatesAllMethodsFromFetchedCurrencyRates(t *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSyncThirdPartyPaymentRatesRejectsInvalidMarkupPercent(t *testing.T) {
|
||||||
|
wallet := &mockPaymentWallet{}
|
||||||
|
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
||||||
|
request := httptest.NewRequest(http.MethodPost, "/admin/payment/third-party-rates/sync", bytes.NewBufferString(`{"markupPercent":-1}`))
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if wallet.lastThirdPartyChannels != nil {
|
||||||
|
t.Fatalf("invalid markup must fail before reading payment methods: %+v", wallet.lastThirdPartyChannels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateRechargeProductForwardsWebCoinSellerAudience(t *testing.T) {
|
func TestCreateRechargeProductForwardsWebCoinSellerAudience(t *testing.T) {
|
||||||
wallet := &mockPaymentWallet{}
|
wallet := &mockPaymentWallet{}
|
||||||
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
router := newPaymentHandlerTestRouter(New(wallet, nil, nil))
|
||||||
@ -227,7 +363,10 @@ func newPaymentHandlerTestRouter(handler *Handler) *gin.Engine {
|
|||||||
c.Set(middleware.ContextUsername, "tester")
|
c.Set(middleware.ContextUsername, "tester")
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
|
router.GET("/admin/payment/recharge-bills", handler.ListRechargeBills)
|
||||||
router.GET("/admin/payment/third-party-channels", handler.ListThirdPartyPaymentChannels)
|
router.GET("/admin/payment/third-party-channels", handler.ListThirdPartyPaymentChannels)
|
||||||
|
router.GET("/admin/payment/temporary-links", handler.ListTemporaryPaymentLinks)
|
||||||
|
router.GET("/admin/payment/temporary-links/:order_id", handler.GetTemporaryPaymentLink)
|
||||||
router.PATCH("/admin/payment/third-party-methods/:method_id/status", handler.SetThirdPartyPaymentMethodStatus)
|
router.PATCH("/admin/payment/third-party-methods/:method_id/status", handler.SetThirdPartyPaymentMethodStatus)
|
||||||
router.PATCH("/admin/payment/third-party-rates/:method_id", handler.UpdateThirdPartyPaymentRate)
|
router.PATCH("/admin/payment/third-party-rates/:method_id", handler.UpdateThirdPartyPaymentRate)
|
||||||
router.POST("/admin/payment/third-party-rates/sync", handler.SyncThirdPartyPaymentRates)
|
router.POST("/admin/payment/third-party-rates/sync", handler.SyncThirdPartyPaymentRates)
|
||||||
@ -240,17 +379,50 @@ type adminPaymentTestResponse struct {
|
|||||||
Data map[string]any `json:"data"`
|
Data map[string]any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectUserIdentityLookup(sqlMock sqlmock.Sqlmock, keyword string, userID int64) {
|
||||||
|
sqlMock.ExpectQuery(`(?s)SELECT u\.user_id.*FROM users u.*LIMIT 1`).
|
||||||
|
WithArgs(
|
||||||
|
"lalu",
|
||||||
|
keyword,
|
||||||
|
keyword,
|
||||||
|
keyword,
|
||||||
|
sqlmock.AnyArg(),
|
||||||
|
keyword,
|
||||||
|
"%"+keyword+"%",
|
||||||
|
keyword,
|
||||||
|
keyword,
|
||||||
|
keyword,
|
||||||
|
sqlmock.AnyArg(),
|
||||||
|
keyword,
|
||||||
|
).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id"}).AddRow(userID))
|
||||||
|
}
|
||||||
|
|
||||||
type mockPaymentWallet struct {
|
type mockPaymentWallet struct {
|
||||||
walletclient.Client
|
walletclient.Client
|
||||||
|
|
||||||
|
lastRechargeBills *walletv1.ListRechargeBillsRequest
|
||||||
|
rechargeBillsResp *walletv1.ListRechargeBillsResponse
|
||||||
lastThirdPartyChannels *walletv1.ListThirdPartyPaymentChannelsRequest
|
lastThirdPartyChannels *walletv1.ListThirdPartyPaymentChannelsRequest
|
||||||
thirdPartyChannelsResp *walletv1.ListThirdPartyPaymentChannelsResponse
|
thirdPartyChannelsResp *walletv1.ListThirdPartyPaymentChannelsResponse
|
||||||
|
lastTemporaryOrders *walletv1.ListTemporaryRechargeOrdersRequest
|
||||||
|
temporaryOrdersResp *walletv1.ListTemporaryRechargeOrdersResponse
|
||||||
|
lastTemporaryOrder *walletv1.GetTemporaryRechargeOrderRequest
|
||||||
|
temporaryOrderResp *walletv1.H5RechargeOrderResponse
|
||||||
lastSetMethodStatus *walletv1.SetThirdPartyPaymentMethodStatusRequest
|
lastSetMethodStatus *walletv1.SetThirdPartyPaymentMethodStatusRequest
|
||||||
lastUpdateRate *walletv1.UpdateThirdPartyPaymentRateRequest
|
lastUpdateRate *walletv1.UpdateThirdPartyPaymentRateRequest
|
||||||
updateRateRequests []*walletv1.UpdateThirdPartyPaymentRateRequest
|
updateRateRequests []*walletv1.UpdateThirdPartyPaymentRateRequest
|
||||||
lastCreateProduct *walletv1.CreateRechargeProductRequest
|
lastCreateProduct *walletv1.CreateRechargeProductRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentWallet) ListRechargeBills(_ context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error) {
|
||||||
|
m.lastRechargeBills = req
|
||||||
|
if m.rechargeBillsResp != nil {
|
||||||
|
return m.rechargeBillsResp, nil
|
||||||
|
}
|
||||||
|
return &walletv1.ListRechargeBillsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
|
func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
|
||||||
m.lastThirdPartyChannels = req
|
m.lastThirdPartyChannels = req
|
||||||
if m.thirdPartyChannelsResp != nil {
|
if m.thirdPartyChannelsResp != nil {
|
||||||
@ -259,6 +431,22 @@ func (m *mockPaymentWallet) ListThirdPartyPaymentChannels(_ context.Context, req
|
|||||||
return &walletv1.ListThirdPartyPaymentChannelsResponse{}, nil
|
return &walletv1.ListThirdPartyPaymentChannelsResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentWallet) ListTemporaryRechargeOrders(_ context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error) {
|
||||||
|
m.lastTemporaryOrders = req
|
||||||
|
if m.temporaryOrdersResp != nil {
|
||||||
|
return m.temporaryOrdersResp, nil
|
||||||
|
}
|
||||||
|
return &walletv1.ListTemporaryRechargeOrdersResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentWallet) GetTemporaryRechargeOrder(_ context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
||||||
|
m.lastTemporaryOrder = req
|
||||||
|
if m.temporaryOrderResp != nil {
|
||||||
|
return m.temporaryOrderResp, nil
|
||||||
|
}
|
||||||
|
return &walletv1.H5RechargeOrderResponse{Order: &walletv1.ExternalRechargeOrder{OrderId: req.GetOrderId(), Status: "pending"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockPaymentWallet) SetThirdPartyPaymentMethodStatus(_ context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
|
func (m *mockPaymentWallet) SetThirdPartyPaymentMethodStatus(_ context.Context, req *walletv1.SetThirdPartyPaymentMethodStatusRequest) (*walletv1.ThirdPartyPaymentMethodResponse, error) {
|
||||||
m.lastSetMethodStatus = req
|
m.lastSetMethodStatus = req
|
||||||
status := "disabled"
|
status := "disabled"
|
||||||
|
|||||||
@ -13,6 +13,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
|
|
||||||
protected.GET("/admin/payment/recharge-bills", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBills)
|
protected.GET("/admin/payment/recharge-bills", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBills)
|
||||||
protected.GET("/admin/payment/third-party-channels", middleware.RequirePermission("payment-third-party:view"), h.ListThirdPartyPaymentChannels)
|
protected.GET("/admin/payment/third-party-channels", middleware.RequirePermission("payment-third-party:view"), h.ListThirdPartyPaymentChannels)
|
||||||
|
protected.GET("/admin/payment/temporary-links", middleware.RequirePermission("payment-temporary-link:view"), h.ListTemporaryPaymentLinks)
|
||||||
|
protected.GET("/admin/payment/temporary-links/:order_id", middleware.RequirePermission("payment-temporary-link:view"), h.GetTemporaryPaymentLink)
|
||||||
protected.PATCH("/admin/payment/third-party-methods/:method_id/status", middleware.RequirePermission("payment-third-party:update"), h.SetThirdPartyPaymentMethodStatus)
|
protected.PATCH("/admin/payment/third-party-methods/:method_id/status", middleware.RequirePermission("payment-third-party:update"), h.SetThirdPartyPaymentMethodStatus)
|
||||||
protected.PATCH("/admin/payment/third-party-rates/:method_id", middleware.RequirePermission("payment-third-party:update"), h.UpdateThirdPartyPaymentRate)
|
protected.PATCH("/admin/payment/third-party-rates/:method_id", middleware.RequirePermission("payment-third-party:update"), h.UpdateThirdPartyPaymentRate)
|
||||||
protected.POST("/admin/payment/third-party-rates/sync", middleware.RequirePermission("payment-third-party:update"), h.SyncThirdPartyPaymentRates)
|
protected.POST("/admin/payment/third-party-rates/sync", middleware.RequirePermission("payment-third-party:update"), h.SyncThirdPartyPaymentRates)
|
||||||
|
|||||||
@ -187,37 +187,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
// 注册奖励领取记录的 owner 是 activity-service;列表关键词先解析成内部 user_id,再保持下游分页和 total 同源。
|
||||||
return 0, false, true
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
}
|
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||||
|
|||||||
@ -139,6 +139,9 @@ type grantDTO struct {
|
|||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
OperatorUserID int64 `json:"operatorUserId"`
|
OperatorUserID int64 `json:"operatorUserId"`
|
||||||
Operator *operatorDTO `json:"operator,omitempty"`
|
Operator *operatorDTO `json:"operator,omitempty"`
|
||||||
|
RevokedAtMS int64 `json:"revokedAtMs"`
|
||||||
|
RevokedByUserID int64 `json:"revokedByUserId,string"`
|
||||||
|
RevokeReason string `json:"revokeReason"`
|
||||||
Items []grantItemDTO `json:"items"`
|
Items []grantItemDTO `json:"items"`
|
||||||
CreatedAtMS int64 `json:"createdAtMs"`
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
UpdatedAtMS int64 `json:"updatedAtMs"`
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
@ -436,6 +439,9 @@ func grantFromProto(grant *walletv1.ResourceGrant) grantDTO {
|
|||||||
Status: grant.GetStatus(),
|
Status: grant.GetStatus(),
|
||||||
Reason: grant.GetReason(),
|
Reason: grant.GetReason(),
|
||||||
OperatorUserID: grant.GetOperatorUserId(),
|
OperatorUserID: grant.GetOperatorUserId(),
|
||||||
|
RevokedAtMS: grant.GetRevokedAtMs(),
|
||||||
|
RevokedByUserID: grant.GetRevokedByUserId(),
|
||||||
|
RevokeReason: grant.GetRevokeReason(),
|
||||||
Items: items,
|
Items: items,
|
||||||
CreatedAtMS: grant.GetCreatedAtMs(),
|
CreatedAtMS: grant.GetCreatedAtMs(),
|
||||||
UpdatedAtMS: grant.GetUpdatedAtMs(),
|
UpdatedAtMS: grant.GetUpdatedAtMs(),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -601,6 +602,28 @@ func (h *Handler) GrantResourceGroup(c *gin.Context) {
|
|||||||
response.Created(c, grant)
|
response.Created(c, grant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) RevokeResourceGrant(c *gin.Context) {
|
||||||
|
grantID := strings.TrimSpace(c.Param("grant_id"))
|
||||||
|
if grantID == "" {
|
||||||
|
response.BadRequest(c, "资源赠送记录不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := h.wallet.RevokeResourceGrant(c.Request.Context(), &walletv1.RevokeResourceGrantRequest{
|
||||||
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
|
GrantId: grantID,
|
||||||
|
Reason: "admin revoke resource grant",
|
||||||
|
OperatorUserId: actorID(c),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
grant := grantFromProto(resp.GetGrant())
|
||||||
|
h.auditLog(c, "revoke-resource-grant", "resource_grants", grant.GrantID, "success", fmt.Sprintf("target_user_id=%d", grant.TargetUserID))
|
||||||
|
response.OK(c, grant)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) LookupResourceGrantTarget(c *gin.Context) {
|
func (h *Handler) LookupResourceGrantTarget(c *gin.Context) {
|
||||||
keyword := strings.TrimSpace(firstQuery(c, "user_id", "userId", "keyword"))
|
keyword := strings.TrimSpace(firstQuery(c, "user_id", "userId", "keyword"))
|
||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
@ -642,13 +665,25 @@ func (h *Handler) LookupResourceGrantTarget(c *gin.Context) {
|
|||||||
|
|
||||||
func (h *Handler) ListResourceGrants(c *gin.Context) {
|
func (h *Handler) ListResourceGrants(c *gin.Context) {
|
||||||
options := shared.ListOptions(c)
|
options := shared.ListOptions(c)
|
||||||
targetUserID, ok := optionalInt64Query(c, "target_user_id", "targetUserId")
|
appCode := appctx.FromContext(c.Request.Context())
|
||||||
if !ok {
|
targetUserID, _, err := shared.ResolveExactUserIDOrNumericFallback(
|
||||||
|
c.Request.Context(),
|
||||||
|
h.userDB,
|
||||||
|
appCode,
|
||||||
|
firstQuery(c, "target_user_id", "targetUserId"),
|
||||||
|
time.Now().UnixMilli(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, shared.ErrInvalidUserIdentityFilter) {
|
||||||
|
response.BadRequest(c, "target_user_id 参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.ServerError(c, "查询赠送用户失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, err := h.wallet.ListResourceGrants(c.Request.Context(), &walletv1.ListResourceGrantsRequest{
|
resp, err := h.wallet.ListResourceGrants(c.Request.Context(), &walletv1.ListResourceGrantsRequest{
|
||||||
RequestId: middleware.CurrentRequestID(c),
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
AppCode: appctx.FromContext(c.Request.Context()),
|
AppCode: appCode,
|
||||||
TargetUserId: targetUserID,
|
TargetUserId: targetUserID,
|
||||||
Status: options.Status,
|
Status: options.Status,
|
||||||
Page: int32(options.Page),
|
Page: int32(options.Page),
|
||||||
@ -704,18 +739,16 @@ func (h *Handler) ListResourceShopPurchaseOrders(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
userKeyword := strings.TrimSpace(firstQuery(c, "user_keyword", "userKeyword", "user"))
|
userKeyword := strings.TrimSpace(firstQuery(c, "user_keyword", "userKeyword", "user"))
|
||||||
if userID == 0 && userKeyword != "" {
|
if userID == 0 && userKeyword != "" {
|
||||||
resolvedUserID, filtered, err := h.resolveResourceShopPurchaseUserID(c.Request.Context(), appCode, userKeyword)
|
resolvedUserID, matched, err := h.resolveResourceShopPurchaseUserID(c.Request.Context(), appCode, userKeyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
response.OK(c, response.Page{Items: []resourceShopPurchaseOrderDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response.ServerError(c, "查询购买用户失败")
|
response.ServerError(c, "查询购买用户失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if filtered {
|
if !matched {
|
||||||
userID = resolvedUserID
|
response.OK(c, response.Page{Items: []resourceShopPurchaseOrderDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
userID = resolvedUserID
|
||||||
}
|
}
|
||||||
ctx, cancel := h.walletRequestContext(c)
|
ctx, cancel := h.walletRequestContext(c)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"hyapp-admin-server/internal/middleware"
|
"hyapp-admin-server/internal/middleware"
|
||||||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -135,6 +136,67 @@ func TestDeleteGiftOnlyCallsGiftConfigDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIdentityAutoGrantConfigRouteDoesNotHitResourceGroupID(t *testing.T) {
|
||||||
|
router := newResourceHandlerTestRouter(New(&mockResourceWallet{}, nil, nil, time.Second, nil))
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/admin/resource-groups/identity-auto-grant-config", nil)
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
// 这里故意使用 nil store,让配置 handler 返回 500;如果路由被 :group_id 抢走,会变成 400 的 ID 参数错误。
|
||||||
|
if recorder.Code != http.StatusInternalServerError {
|
||||||
|
t.Fatalf("identity config route should hit config handler, got %d %s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if strings.Contains(recorder.Body.String(), "ID 参数不正确") {
|
||||||
|
t.Fatalf("identity config route should not be parsed as group_id: %s", recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListResourceGrantsResolvesDisplayIDBeforeWalletFilter(t *testing.T) {
|
||||||
|
db, sqlMock, err := sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create sql mock failed: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
wallet := &mockResourceWallet{}
|
||||||
|
router := newResourceHandlerTestRouter(New(wallet, nil, db, time.Second, nil))
|
||||||
|
resolvedUserID := int64(327702592329093120)
|
||||||
|
sqlMock.ExpectQuery(`(?s)SELECT u\.user_id.*FROM users u.*LIMIT 1`).
|
||||||
|
WithArgs(
|
||||||
|
"lalu",
|
||||||
|
"111",
|
||||||
|
"111",
|
||||||
|
"111",
|
||||||
|
sqlmock.AnyArg(),
|
||||||
|
"111",
|
||||||
|
"%111%",
|
||||||
|
"111",
|
||||||
|
"111",
|
||||||
|
"111",
|
||||||
|
sqlmock.AnyArg(),
|
||||||
|
"111",
|
||||||
|
).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id"}).AddRow(resolvedUserID))
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/admin/resource-grants?target_user_id=111", nil)
|
||||||
|
router.ServeHTTP(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("list grants status mismatch: %d %s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if len(wallet.listGrantRequests) != 1 {
|
||||||
|
t.Fatalf("expected one wallet list request, got %d", len(wallet.listGrantRequests))
|
||||||
|
}
|
||||||
|
if got := wallet.listGrantRequests[0].GetTargetUserId(); got != resolvedUserID {
|
||||||
|
t.Fatalf("target display id should be resolved before wallet filter: got %d want %d", got, resolvedUserID)
|
||||||
|
}
|
||||||
|
if err := sqlMock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Fatalf("sql expectations mismatch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newResourceHandlerTestRouter(handler *Handler) *gin.Engine {
|
func newResourceHandlerTestRouter(handler *Handler) *gin.Engine {
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
@ -143,18 +205,39 @@ func newResourceHandlerTestRouter(handler *Handler) *gin.Engine {
|
|||||||
c.Set(middleware.ContextRequestID, "resource-handler-test")
|
c.Set(middleware.ContextRequestID, "resource-handler-test")
|
||||||
c.Set(middleware.ContextUserID, uint(7))
|
c.Set(middleware.ContextUserID, uint(7))
|
||||||
c.Set(middleware.ContextUsername, "tester")
|
c.Set(middleware.ContextUsername, "tester")
|
||||||
|
// 测试路由走真实 RegisterRoutes,权限一次性放开,避免中间件短路导致无法覆盖路由优先级。
|
||||||
|
c.Set(middleware.ContextPermissions, []string{
|
||||||
|
"emoji-pack:create",
|
||||||
|
"emoji-pack:view",
|
||||||
|
"gift:delete",
|
||||||
|
"gift:view",
|
||||||
|
"gift:update",
|
||||||
|
"gift:create",
|
||||||
|
"gift:status",
|
||||||
|
"resource:create",
|
||||||
|
"resource-grant:create",
|
||||||
|
"resource-grant:revoke",
|
||||||
|
"resource-grant:view",
|
||||||
|
"resource-group:create",
|
||||||
|
"resource-group:update",
|
||||||
|
"resource-group:view",
|
||||||
|
"resource-shop:update",
|
||||||
|
"resource-shop:view",
|
||||||
|
"resource:update",
|
||||||
|
"resource:view",
|
||||||
|
})
|
||||||
c.Next()
|
c.Next()
|
||||||
})
|
})
|
||||||
router.PUT("/admin/resources/mp4-layouts/batch", handler.UpdateMP4ResourceLayouts)
|
RegisterRoutes(router.Group(""), handler)
|
||||||
router.DELETE("/admin/gifts/:gift_id", handler.DeleteGift)
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockResourceWallet struct {
|
type mockResourceWallet struct {
|
||||||
walletclient.Client
|
walletclient.Client
|
||||||
resources map[int64]*walletv1.Resource
|
resources map[int64]*walletv1.Resource
|
||||||
updates []*walletv1.UpdateResourceRequest
|
updates []*walletv1.UpdateResourceRequest
|
||||||
deletedGifts []*walletv1.DeleteGiftConfigRequest
|
deletedGifts []*walletv1.DeleteGiftConfigRequest
|
||||||
|
listGrantRequests []*walletv1.ListResourceGrantsRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) {
|
func (m *mockResourceWallet) GetResource(ctx context.Context, req *walletv1.GetResourceRequest) (*walletv1.GetResourceResponse, error) {
|
||||||
@ -200,3 +283,10 @@ func (m *mockResourceWallet) DeleteGiftConfig(ctx context.Context, req *walletv1
|
|||||||
Name: "Rose",
|
Name: "Rose",
|
||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockResourceWallet) ListResourceGrants(ctx context.Context, req *walletv1.ListResourceGrantsRequest) (*walletv1.ListResourceGrantsResponse, error) {
|
||||||
|
// 列表过滤的修正点发生在 admin-server 调 wallet 之前;测试只需要捕获请求形状,
|
||||||
|
// 返回空页即可避免后续用户资料补全查询干扰断言。
|
||||||
|
m.listGrantRequests = append(m.listGrantRequests, req)
|
||||||
|
return &walletv1.ListResourceGrantsResponse{Grants: []*walletv1.ResourceGrant{}, Total: 0}, nil
|
||||||
|
}
|
||||||
|
|||||||
304
server/admin/internal/modules/resource/identity_auto_grant.go
Normal file
304
server/admin/internal/modules/resource/identity_auto_grant.go
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/appctx"
|
||||||
|
"hyapp-admin-server/internal/middleware"
|
||||||
|
"hyapp-admin-server/internal/model"
|
||||||
|
"hyapp-admin-server/internal/response"
|
||||||
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const resourceIdentityAutoGrantConfigGroup = "resource-identity-auto-grant-config"
|
||||||
|
|
||||||
|
var resourceIdentityAutoGrantTypes = []string{"host", "bd", "bd_leader", "agency", "manager"}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantConfigDTO struct {
|
||||||
|
Items []resourceIdentityAutoGrantItemDTO `json:"items"`
|
||||||
|
UpdatedByAdminID int64 `json:"updatedByAdminId"`
|
||||||
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
|
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantItemDTO struct {
|
||||||
|
IdentityType string `json:"identityType"`
|
||||||
|
ResourceGroupID int64 `json:"resourceGroupId"`
|
||||||
|
ResourceGroup *resourceGroupDTO `json:"resourceGroup,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantConfigValue struct {
|
||||||
|
Items []resourceIdentityAutoGrantStoredItem `json:"items"`
|
||||||
|
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantStoredItem struct {
|
||||||
|
IdentityType string `json:"identity_type"`
|
||||||
|
ResourceGroupID int64 `json:"resource_group_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantConfigRequest struct {
|
||||||
|
Items []resourceIdentityAutoGrantItemRequest `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceIdentityAutoGrantItemRequest struct {
|
||||||
|
IdentityType string `json:"identity_type"`
|
||||||
|
IdentityTypeCamel string `json:"identityType"`
|
||||||
|
ResourceGroupID int64 `json:"resource_group_id"`
|
||||||
|
ResourceGroupIDJS int64 `json:"resourceGroupId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetResourceIdentityAutoGrantConfig(c *gin.Context) {
|
||||||
|
if h.store == nil {
|
||||||
|
response.ServerError(c, "后台配置存储未初始化")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 身份资源组属于 admin 后台低频配置,不进入 wallet 协议;读取时从 admin_app_configs 取当前 App 的快照。
|
||||||
|
config, err := h.loadIdentityAutoGrantConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "加载身份资源组配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UpdateResourceIdentityAutoGrantConfig(c *gin.Context) {
|
||||||
|
if h.store == nil {
|
||||||
|
response.ServerError(c, "后台配置存储未初始化")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req resourceIdentityAutoGrantConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "身份资源组配置参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前端保存的是整份配置;后端也要求 5 个身份一次性提交,避免半包请求把未提交身份误清空。
|
||||||
|
items, err := normalizeIdentityAutoGrantItems(req.Items)
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// resource_group_id=0 表示不自动发放;大于 0 必须先确认 wallet 中存在,防止保存悬空资源组。
|
||||||
|
if err := h.validateIdentityAutoGrantResourceGroups(c, items); err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(resourceIdentityAutoGrantConfigValue{
|
||||||
|
Items: items,
|
||||||
|
UpdatedByAdminID: actorID(c),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "保存身份资源组配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appCode := appctx.FromContext(c.Request.Context())
|
||||||
|
// 配置按 App 隔离;Key 使用中间件解析出的 app_code,禁止请求体自行覆盖作用域。
|
||||||
|
item := model.AppConfig{
|
||||||
|
Group: resourceIdentityAutoGrantConfigGroup,
|
||||||
|
Key: appCode,
|
||||||
|
Value: string(payload),
|
||||||
|
Description: "用户身份自动发放资源组配置",
|
||||||
|
}
|
||||||
|
if err := h.store.UpsertAppConfigs([]model.AppConfig{item}); err != nil {
|
||||||
|
response.ServerError(c, "保存身份资源组配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存后重新读取并补资源组详情,保证返回值和 GET 接口结构一致,前端无需自己拼保存后的展示数据。
|
||||||
|
config, err := h.loadIdentityAutoGrantConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "加载身份资源组配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.auditLog(c, "update-identity-auto-grant-config", "resource_identity_auto_grant_configs", appCode, "success", identityAutoGrantAuditDetail(items))
|
||||||
|
response.OK(c, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) loadIdentityAutoGrantConfig(c *gin.Context) (resourceIdentityAutoGrantConfigDTO, error) {
|
||||||
|
appCode := appctx.FromContext(c.Request.Context())
|
||||||
|
item, err := h.store.GetAppConfig(resourceIdentityAutoGrantConfigGroup, appCode)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// 首次进入页面没有配置行时返回完整身份集合,前端可以直接展示空配置而不是走错误态。
|
||||||
|
return resourceIdentityAutoGrantConfigDTO{Items: defaultIdentityAutoGrantItems()}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return resourceIdentityAutoGrantConfigDTO{}, err
|
||||||
|
}
|
||||||
|
config, err := identityAutoGrantConfigFromValue(item.Value)
|
||||||
|
if err != nil {
|
||||||
|
return resourceIdentityAutoGrantConfigDTO{}, err
|
||||||
|
}
|
||||||
|
config.CreatedAtMS = item.CreatedAtMS
|
||||||
|
config.UpdatedAtMS = item.UpdatedAtMS
|
||||||
|
// 资源组详情只用于展示已选项名称;补全失败不应该让整个配置页打不开。
|
||||||
|
h.fillIdentityAutoGrantResourceGroups(c, config.Items)
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func identityAutoGrantConfigFromValue(raw string) (resourceIdentityAutoGrantConfigDTO, error) {
|
||||||
|
var value resourceIdentityAutoGrantConfigValue
|
||||||
|
if strings.TrimSpace(raw) != "" {
|
||||||
|
if err := json.Unmarshal([]byte(raw), &value); err != nil {
|
||||||
|
return resourceIdentityAutoGrantConfigDTO{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsByType := make(map[string]resourceIdentityAutoGrantStoredItem, len(value.Items))
|
||||||
|
for _, item := range value.Items {
|
||||||
|
identityType := strings.TrimSpace(item.IdentityType)
|
||||||
|
if isIdentityAutoGrantType(identityType) && item.ResourceGroupID >= 0 {
|
||||||
|
// 读取历史配置时按当前支持的身份集合过滤,旧脏数据不继续扩散到前端表单。
|
||||||
|
itemsByType[identityType] = resourceIdentityAutoGrantStoredItem{IdentityType: identityType, ResourceGroupID: item.ResourceGroupID}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := make([]resourceIdentityAutoGrantItemDTO, 0, len(resourceIdentityAutoGrantTypes))
|
||||||
|
for _, identityType := range resourceIdentityAutoGrantTypes {
|
||||||
|
// 输出顺序固定,避免前端每次打开弹窗时身份行抖动。
|
||||||
|
item := itemsByType[identityType]
|
||||||
|
items = append(items, resourceIdentityAutoGrantItemDTO{
|
||||||
|
IdentityType: identityType,
|
||||||
|
ResourceGroupID: item.ResourceGroupID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resourceIdentityAutoGrantConfigDTO{Items: items, UpdatedByAdminID: value.UpdatedByAdminID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIdentityAutoGrantItems() []resourceIdentityAutoGrantItemDTO {
|
||||||
|
items := make([]resourceIdentityAutoGrantItemDTO, 0, len(resourceIdentityAutoGrantTypes))
|
||||||
|
for _, identityType := range resourceIdentityAutoGrantTypes {
|
||||||
|
items = append(items, resourceIdentityAutoGrantItemDTO{IdentityType: identityType})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeIdentityAutoGrantItems(items []resourceIdentityAutoGrantItemRequest) ([]resourceIdentityAutoGrantStoredItem, error) {
|
||||||
|
if len(items) != len(resourceIdentityAutoGrantTypes) {
|
||||||
|
return nil, fmt.Errorf("必须配置所有身份")
|
||||||
|
}
|
||||||
|
|
||||||
|
itemByType := make(map[string]resourceIdentityAutoGrantStoredItem, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
// 当前前端提交 snake_case;这里兼容 camelCase,避免旧构建或调试脚本因为字段名差异无法保存。
|
||||||
|
identityType := strings.TrimSpace(firstNonEmpty(item.IdentityType, item.IdentityTypeCamel))
|
||||||
|
if !isIdentityAutoGrantType(identityType) {
|
||||||
|
return nil, fmt.Errorf("身份参数不正确")
|
||||||
|
}
|
||||||
|
if _, exists := itemByType[identityType]; exists {
|
||||||
|
return nil, fmt.Errorf("身份不能重复配置")
|
||||||
|
}
|
||||||
|
// 0 是显式关闭自动发放,所以只拒绝负数;正数存在性在后续 wallet 查询里验证。
|
||||||
|
resourceGroupID := item.ResourceGroupID
|
||||||
|
if resourceGroupID == 0 {
|
||||||
|
resourceGroupID = item.ResourceGroupIDJS
|
||||||
|
}
|
||||||
|
if resourceGroupID < 0 {
|
||||||
|
return nil, fmt.Errorf("资源组必须是有效资源组")
|
||||||
|
}
|
||||||
|
itemByType[identityType] = resourceIdentityAutoGrantStoredItem{
|
||||||
|
IdentityType: identityType,
|
||||||
|
ResourceGroupID: resourceGroupID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := make([]resourceIdentityAutoGrantStoredItem, 0, len(resourceIdentityAutoGrantTypes))
|
||||||
|
for _, identityType := range resourceIdentityAutoGrantTypes {
|
||||||
|
// 按白名单顺序重建数组,存储层不依赖前端传入顺序。
|
||||||
|
item, ok := itemByType[identityType]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("必须配置所有身份")
|
||||||
|
}
|
||||||
|
normalized = append(normalized, item)
|
||||||
|
}
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) validateIdentityAutoGrantResourceGroups(c *gin.Context, items []resourceIdentityAutoGrantStoredItem) error {
|
||||||
|
if h.wallet == nil {
|
||||||
|
for _, item := range items {
|
||||||
|
if item.ResourceGroupID > 0 {
|
||||||
|
// 没有 wallet client 时只能接受全关闭配置,避免把未校验的正数资源组写入运行配置。
|
||||||
|
return fmt.Errorf("资源组服务未初始化")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
checked := make(map[int64]struct{}, len(items))
|
||||||
|
ctx, cancel := h.walletRequestContext(c)
|
||||||
|
defer cancel()
|
||||||
|
for _, item := range items {
|
||||||
|
if item.ResourceGroupID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := checked[item.ResourceGroupID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checked[item.ResourceGroupID] = struct{}{}
|
||||||
|
// 保存前只校验唯一资源组 ID,一份配置里多个身份共用同组时不重复打 wallet。
|
||||||
|
_, err := h.wallet.GetResourceGroup(ctx, &walletv1.GetResourceGroupRequest{
|
||||||
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
|
GroupId: item.ResourceGroupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("资源组 %d 不存在或不可用", item.ResourceGroupID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) fillIdentityAutoGrantResourceGroups(c *gin.Context, items []resourceIdentityAutoGrantItemDTO) {
|
||||||
|
if h.wallet == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupByID := make(map[int64]*resourceGroupDTO, len(items))
|
||||||
|
ctx, cancel := h.walletRequestContext(c)
|
||||||
|
defer cancel()
|
||||||
|
for index := range items {
|
||||||
|
groupID := items[index].ResourceGroupID
|
||||||
|
if groupID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if group, ok := groupByID[groupID]; ok {
|
||||||
|
items[index].ResourceGroup = group
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 已保存配置可能指向被禁用或被删除的资源组;详情查询失败时保留 ID,让前端至少能展示 #ID 并允许重新选择。
|
||||||
|
resp, err := h.wallet.GetResourceGroup(ctx, &walletv1.GetResourceGroupRequest{
|
||||||
|
RequestId: middleware.CurrentRequestID(c),
|
||||||
|
AppCode: appctx.FromContext(c.Request.Context()),
|
||||||
|
GroupId: groupID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
group := resourceGroupFromProto(resp.GetGroup())
|
||||||
|
groupByID[groupID] = &group
|
||||||
|
items[index].ResourceGroup = &group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentityAutoGrantType(value string) bool {
|
||||||
|
for _, candidate := range resourceIdentityAutoGrantTypes {
|
||||||
|
if value == candidate {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func identityAutoGrantAuditDetail(items []resourceIdentityAutoGrantStoredItem) string {
|
||||||
|
parts := make([]string, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
parts = append(parts, fmt.Sprintf("%s=%d", item.IdentityType, item.ResourceGroupID))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -163,29 +162,11 @@ func (h *Handler) resolveResourceShopPurchaseUserID(ctx context.Context, appCode
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
}
|
}
|
||||||
if h == nil || h.userDB == nil {
|
if h == nil {
|
||||||
return 0, false, fmt.Errorf("user mysql is not configured")
|
return 0, false, nil
|
||||||
}
|
}
|
||||||
nowMs := time.Now().UnixMilli()
|
// 资源商店购买记录同样由 wallet-service 按内部 user_id 过滤;后台输入允许短号、靓号和昵称,先解析再下传。
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
return shared.ResolveExactUserID(ctx, h.userDB, appCode, keyword, time.Now().UnixMilli())
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
row := h.userDB.QueryRowContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
var userID int64
|
|
||||||
if err := row.Scan(&userID); err != nil {
|
|
||||||
return 0, true, err
|
|
||||||
}
|
|
||||||
return userID, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) enrichGrantOperators(ctx context.Context, grants []grantDTO) error {
|
func (h *Handler) enrichGrantOperators(ctx context.Context, grants []grantDTO) error {
|
||||||
|
|||||||
@ -25,6 +25,8 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
|
|
||||||
protected.GET("/admin/resource-groups", middleware.RequirePermission("resource-group:view"), h.ListResourceGroups)
|
protected.GET("/admin/resource-groups", middleware.RequirePermission("resource-group:view"), h.ListResourceGroups)
|
||||||
protected.POST("/admin/resource-groups", middleware.RequirePermission("resource-group:create"), h.CreateResourceGroup)
|
protected.POST("/admin/resource-groups", middleware.RequirePermission("resource-group:create"), h.CreateResourceGroup)
|
||||||
|
protected.GET("/admin/resource-groups/identity-auto-grant-config", middleware.RequirePermission("resource-group:view"), h.GetResourceIdentityAutoGrantConfig)
|
||||||
|
protected.PUT("/admin/resource-groups/identity-auto-grant-config", middleware.RequirePermission("resource-group:update"), h.UpdateResourceIdentityAutoGrantConfig)
|
||||||
protected.GET("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:view"), h.GetResourceGroup)
|
protected.GET("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:view"), h.GetResourceGroup)
|
||||||
protected.PUT("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup)
|
protected.PUT("/admin/resource-groups/:group_id", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup)
|
||||||
protected.PUT("/admin/resource-groups/:group_id/items", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup)
|
protected.PUT("/admin/resource-groups/:group_id/items", middleware.RequirePermission("resource-group:update"), h.UpdateResourceGroup)
|
||||||
@ -43,6 +45,7 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
|
|
||||||
protected.POST("/admin/resource-grants/resource", middleware.RequirePermission("resource-grant:create"), h.GrantResource)
|
protected.POST("/admin/resource-grants/resource", middleware.RequirePermission("resource-grant:create"), h.GrantResource)
|
||||||
protected.POST("/admin/resource-grants/group", middleware.RequirePermission("resource-grant:create"), h.GrantResourceGroup)
|
protected.POST("/admin/resource-grants/group", middleware.RequirePermission("resource-grant:create"), h.GrantResourceGroup)
|
||||||
|
protected.POST("/admin/resource-grants/:grant_id/revoke", middleware.RequirePermission("resource-grant:revoke"), h.RevokeResourceGrant)
|
||||||
protected.GET("/admin/resource-grants/target", middleware.RequirePermission("resource-grant:create"), h.LookupResourceGrantTarget)
|
protected.GET("/admin/resource-grants/target", middleware.RequirePermission("resource-grant:create"), h.LookupResourceGrantTarget)
|
||||||
protected.GET("/admin/resource-grants", middleware.RequirePermission("resource-grant:view"), h.ListResourceGrants)
|
protected.GET("/admin/resource-grants", middleware.RequirePermission("resource-grant:view"), h.ListResourceGrants)
|
||||||
|
|
||||||
|
|||||||
61
server/admin/internal/modules/riskconfig/handler.go
Normal file
61
server/admin/internal/modules/riskconfig/handler.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package riskconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/integration/userclient"
|
||||||
|
"hyapp-admin-server/internal/middleware"
|
||||||
|
"hyapp-admin-server/internal/modules/shared"
|
||||||
|
"hyapp-admin-server/internal/response"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
user userclient.Client
|
||||||
|
audit shared.OperationLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(user userclient.Client, audit shared.OperationLogger) *Handler {
|
||||||
|
return &Handler{user: user, audit: audit}
|
||||||
|
}
|
||||||
|
|
||||||
|
type registerRiskConfigRequest struct {
|
||||||
|
MaxAccountsPerDevice int32 `json:"max_accounts_per_device"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetRegisterRiskConfig(c *gin.Context) {
|
||||||
|
config, err := h.user.GetRegisterRiskConfig(c.Request.Context(), userclient.GetRegisterRiskConfigRequest{
|
||||||
|
RequestID: middleware.CurrentRequestID(c),
|
||||||
|
Caller: "admin-server",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, "获取风控配置失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UpdateRegisterRiskConfig(c *gin.Context) {
|
||||||
|
var req registerRiskConfigRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "风控配置参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.MaxAccountsPerDevice < 1 {
|
||||||
|
response.BadRequest(c, "单设备最大账号数必须大于 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config, err := h.user.UpdateRegisterRiskConfig(c.Request.Context(), userclient.UpdateRegisterRiskConfigRequest{
|
||||||
|
RequestID: middleware.CurrentRequestID(c),
|
||||||
|
Caller: "admin-server",
|
||||||
|
MaxAccountsPerDevice: req.MaxAccountsPerDevice,
|
||||||
|
OperatorAdminID: int64(middleware.CurrentUserID(c)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shared.OperationLog(c, h.audit, "update-risk-config", "auth_risk_configs", "success", fmt.Sprintf("max_accounts_per_device=%d", config.MaxAccountsPerDevice))
|
||||||
|
response.OK(c, config)
|
||||||
|
}
|
||||||
16
server/admin/internal/modules/riskconfig/routes.go
Normal file
16
server/admin/internal/modules/riskconfig/routes.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package riskconfig
|
||||||
|
|
||||||
|
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/users/risk-config", middleware.RequirePermission("risk-config:view"), h.GetRegisterRiskConfig)
|
||||||
|
protected.PUT("/admin/users/risk-config", middleware.RequirePermission("risk-config:update"), h.UpdateRegisterRiskConfig)
|
||||||
|
}
|
||||||
@ -202,41 +202,16 @@ func (h *Handler) resolveOwnerUserID(ctx context.Context, keyword string) (int64
|
|||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
if h.userDB == nil {
|
||||||
// userDB 缺失时只允许长 ID 精确筛选,避免把短号或昵称误当成 settlement.owner_user_id 查询。
|
// userDB 缺失时只允许正整数长 ID 精确筛选,避免把短号或昵称误当成 settlement.owner_user_id 查询。
|
||||||
if parsed, err := strconv.ParseInt(keyword, 10, 64); err == nil && parsed > 0 {
|
userID, matched, err := shared.ResolveExactUserIDOrNumericFallback(ctx, nil, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
return parsed, true, true
|
return userID, matched, err == nil
|
||||||
}
|
|
||||||
return 0, false, true
|
|
||||||
}
|
}
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
// 表头筛选先把运营输入解析成唯一房主 user_id,再交给 activity-service 的 owner_user_id 条件,保证分页 total 和列表数据同源。
|
// 表头筛选先把运营输入解析成唯一房主 user_id,再交给 activity-service 的 owner_user_id 条件,保证分页 total 和列表数据同源。
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillOwnerUsers(ctx context.Context, settlements []settlementDTO) error {
|
func (h *Handler) fillOwnerUsers(ctx context.Context, settlements []settlementDTO) error {
|
||||||
|
|||||||
@ -163,37 +163,12 @@ func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string) (int64
|
|||||||
if keyword == "" {
|
if keyword == "" {
|
||||||
return 0, false, true
|
return 0, false, true
|
||||||
}
|
}
|
||||||
if h.userDB == nil {
|
// 签到领取记录按 activity-service 的内部 user_id 查;短号、靓号和昵称先在 admin 侧解析,避免下游误用展示 ID。
|
||||||
return 0, false, true
|
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
|
||||||
}
|
|
||||||
appCode := appctx.FromContext(ctx)
|
|
||||||
nowMs := time.Now().UnixMilli()
|
|
||||||
matchSQL, matchArgs := shared.UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
orderSQL, orderArgs := shared.UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
|
||||||
args := append([]any{appCode}, matchArgs...)
|
|
||||||
args = append(args, orderArgs...)
|
|
||||||
rows, err := h.userDB.QueryContext(ctx, `
|
|
||||||
SELECT u.user_id
|
|
||||||
FROM users u
|
|
||||||
WHERE u.app_code = ? AND `+matchSQL+`
|
|
||||||
ORDER BY
|
|
||||||
`+orderSQL+`,
|
|
||||||
u.user_id DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false, false
|
return 0, false, false
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
return userID, matched, true
|
||||||
if !rows.Next() {
|
|
||||||
return 0, false, rows.Err() == nil
|
|
||||||
}
|
|
||||||
var userID int64
|
|
||||||
if err := rows.Scan(&userID); err != nil {
|
|
||||||
return 0, false, false
|
|
||||||
}
|
|
||||||
return userID, true, rows.Err() == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidUserIdentityFilter = errors.New("invalid user identity filter")
|
||||||
|
|
||||||
|
// ResolveExactUserID 把后台输入框里的用户身份解析成 owner service 使用的内部 user_id。
|
||||||
|
// 查询条件复用统一身份 SQL:精确长 ID、当前展示号、默认短号、active 靓号优先,昵称只做最后的模糊兜底。
|
||||||
|
func ResolveExactUserID(ctx context.Context, db *sql.DB, appCode string, keyword string, nowMs int64) (int64, bool, error) {
|
||||||
|
keyword = strings.TrimSpace(keyword)
|
||||||
|
if keyword == "" || db == nil {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
matchSQL, matchArgs := UserIdentityExactSQL("u", "u.user_id", keyword, nowMs)
|
||||||
|
orderSQL, orderArgs := UserIdentityExactOrderSQL("u", "u.user_id", keyword, nowMs)
|
||||||
|
args := append([]any{appCode}, matchArgs...)
|
||||||
|
args = append(args, orderArgs...)
|
||||||
|
row := db.QueryRowContext(ctx, `
|
||||||
|
SELECT u.user_id
|
||||||
|
FROM users u
|
||||||
|
WHERE u.app_code = ? AND `+matchSQL+`
|
||||||
|
ORDER BY
|
||||||
|
`+orderSQL+`,
|
||||||
|
u.user_id DESC
|
||||||
|
LIMIT 1`,
|
||||||
|
args...,
|
||||||
|
)
|
||||||
|
var userID int64
|
||||||
|
if err := row.Scan(&userID); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
return userID, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveExactUserIDOrNumericFallback 用于列表 owner 在 wallet/activity 等下游服务的场景。
|
||||||
|
// 下游只认内部 user_id,后台先按短号/靓号解析;解析不到时只接受正整数长 ID,兼容用户资料缺失的历史事实记录。
|
||||||
|
func ResolveExactUserIDOrNumericFallback(ctx context.Context, db *sql.DB, appCode string, keyword string, nowMs int64) (int64, bool, error) {
|
||||||
|
keyword = strings.TrimSpace(keyword)
|
||||||
|
if keyword == "" {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
if db != nil {
|
||||||
|
userID, matched, err := ResolveExactUserID(ctx, db, appCode, keyword, nowMs)
|
||||||
|
if err != nil || matched {
|
||||||
|
return userID, matched, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(keyword, 10, 64)
|
||||||
|
if err != nil || userID <= 0 {
|
||||||
|
return 0, true, fmt.Errorf("%w: %s", ErrInvalidUserIdentityFilter, keyword)
|
||||||
|
}
|
||||||
|
return userID, true, nil
|
||||||
|
}
|
||||||
@ -26,6 +26,8 @@ var defaultPermissions = []model.Permission{
|
|||||||
{Name: "靓号池更新", Code: "pretty-id:update", Kind: "button"},
|
{Name: "靓号池更新", Code: "pretty-id:update", Kind: "button"},
|
||||||
{Name: "靓号批量生成", Code: "pretty-id:generate", Kind: "button"},
|
{Name: "靓号批量生成", Code: "pretty-id:generate", Kind: "button"},
|
||||||
{Name: "靓号后台发放", Code: "pretty-id:grant", Kind: "button"},
|
{Name: "靓号后台发放", Code: "pretty-id:grant", Kind: "button"},
|
||||||
|
{Name: "风控配置查看", Code: "risk-config:view", Kind: "menu"},
|
||||||
|
{Name: "风控配置更新", Code: "risk-config:update", Kind: "button"},
|
||||||
{Name: "地区屏蔽查看", Code: "region-block:view", Kind: "menu"},
|
{Name: "地区屏蔽查看", Code: "region-block:view", Kind: "menu"},
|
||||||
{Name: "地区屏蔽更新", Code: "region-block:update", Kind: "button"},
|
{Name: "地区屏蔽更新", Code: "region-block:update", Kind: "button"},
|
||||||
{Name: "房间查看", Code: "room:view", Kind: "menu"},
|
{Name: "房间查看", Code: "room:view", Kind: "menu"},
|
||||||
@ -57,6 +59,7 @@ var defaultPermissions = []model.Permission{
|
|||||||
{Name: "资源组更新", Code: "resource-group:update", Kind: "button"},
|
{Name: "资源组更新", Code: "resource-group:update", Kind: "button"},
|
||||||
{Name: "资源赠送查看", Code: "resource-grant:view", Kind: "menu"},
|
{Name: "资源赠送查看", Code: "resource-grant:view", Kind: "menu"},
|
||||||
{Name: "资源赠送创建", Code: "resource-grant:create", Kind: "button"},
|
{Name: "资源赠送创建", Code: "resource-grant:create", Kind: "button"},
|
||||||
|
{Name: "资源赠送撤销", Code: "resource-grant:revoke", Kind: "button"},
|
||||||
{Name: "礼物查看", Code: "gift:view", Kind: "menu"},
|
{Name: "礼物查看", Code: "gift:view", Kind: "menu"},
|
||||||
{Name: "礼物创建", Code: "gift:create", Kind: "button"},
|
{Name: "礼物创建", Code: "gift:create", Kind: "button"},
|
||||||
{Name: "礼物更新", Code: "gift:update", Kind: "button"},
|
{Name: "礼物更新", Code: "gift:update", Kind: "button"},
|
||||||
@ -108,6 +111,7 @@ var defaultPermissions = []model.Permission{
|
|||||||
{Name: "支付账单查看", Code: "payment-bill:view", Kind: "menu"},
|
{Name: "支付账单查看", Code: "payment-bill:view", Kind: "menu"},
|
||||||
{Name: "三方支付查看", Code: "payment-third-party:view", Kind: "menu"},
|
{Name: "三方支付查看", Code: "payment-third-party:view", Kind: "menu"},
|
||||||
{Name: "三方支付更新", Code: "payment-third-party:update", Kind: "button"},
|
{Name: "三方支付更新", Code: "payment-third-party:update", Kind: "button"},
|
||||||
|
{Name: "三方临时支付链接查看", Code: "payment-temporary-link:view", Kind: "menu"},
|
||||||
{Name: "内购配置查看", Code: "payment-product:view", Kind: "menu"},
|
{Name: "内购配置查看", Code: "payment-product:view", Kind: "menu"},
|
||||||
{Name: "内购配置创建", Code: "payment-product:create", Kind: "button"},
|
{Name: "内购配置创建", Code: "payment-product:create", Kind: "button"},
|
||||||
{Name: "内购配置更新", Code: "payment-product:update", Kind: "button"},
|
{Name: "内购配置更新", Code: "payment-product:update", Kind: "button"},
|
||||||
@ -139,6 +143,8 @@ var defaultPermissions = []model.Permission{
|
|||||||
{Name: "红包配置更新", Code: "red-packet:update", Kind: "button"},
|
{Name: "红包配置更新", Code: "red-packet:update", Kind: "button"},
|
||||||
{Name: "CP 配置查看", Code: "cp-config:view", Kind: "menu"},
|
{Name: "CP 配置查看", Code: "cp-config:view", Kind: "menu"},
|
||||||
{Name: "CP 配置更新", Code: "cp-config:update", Kind: "button"},
|
{Name: "CP 配置更新", Code: "cp-config:update", Kind: "button"},
|
||||||
|
{Name: "CP 排行活动查看", Code: "cp-weekly-rank:view", Kind: "menu"},
|
||||||
|
{Name: "CP 排行活动更新", Code: "cp-weekly-rank:update", Kind: "button"},
|
||||||
{Name: "VIP 配置查看", Code: "vip-config:view", Kind: "menu"},
|
{Name: "VIP 配置查看", Code: "vip-config:view", Kind: "menu"},
|
||||||
{Name: "VIP 配置更新", Code: "vip-config:update", Kind: "button"},
|
{Name: "VIP 配置更新", Code: "vip-config:update", Kind: "button"},
|
||||||
{Name: "VIP 赠送", Code: "vip-config:grant", Kind: "button"},
|
{Name: "VIP 赠送", Code: "vip-config:grant", Kind: "button"},
|
||||||
@ -263,7 +269,8 @@ func (s *Store) seedMenus() error {
|
|||||||
{ParentID: &appUsersID, Title: "登录日志", Code: "app-user-login-logs", Path: "/app/users/login-logs", Icon: "login", PermissionCode: "app-user:view", Sort: 62, Visible: true},
|
{ParentID: &appUsersID, Title: "登录日志", Code: "app-user-login-logs", Path: "/app/users/login-logs", Icon: "login", PermissionCode: "app-user:view", Sort: 62, Visible: true},
|
||||||
{ParentID: &appUsersID, Title: "等级配置", Code: "app-user-level-config", Path: "/app/users/level-config", Icon: "military_tech", PermissionCode: "level-config:view", Sort: 63, Visible: true},
|
{ParentID: &appUsersID, Title: "等级配置", Code: "app-user-level-config", Path: "/app/users/level-config", Icon: "military_tech", PermissionCode: "level-config:view", Sort: 63, Visible: true},
|
||||||
{ParentID: &appUsersID, Title: "靓号管理", Code: "app-user-pretty-ids", Path: "/app/users/pretty-ids", Icon: "tag", PermissionCode: "pretty-id:view", Sort: 64, Visible: true},
|
{ParentID: &appUsersID, Title: "靓号管理", Code: "app-user-pretty-ids", Path: "/app/users/pretty-ids", Icon: "tag", PermissionCode: "pretty-id:view", Sort: 64, Visible: true},
|
||||||
{ParentID: &appUsersID, Title: "地区屏蔽", Code: "app-user-region-blocks", Path: "/app/users/region-blocks", Icon: "public", PermissionCode: "region-block:view", Sort: 65, Visible: true},
|
{ParentID: &appUsersID, Title: "风控管理", Code: "app-user-risk-config", Path: "/app/users/risk-config", Icon: "shield", PermissionCode: "risk-config:view", Sort: 65, Visible: true},
|
||||||
|
{ParentID: &appUsersID, Title: "地区屏蔽", Code: "app-user-region-blocks", Path: "/app/users/region-blocks", Icon: "public", PermissionCode: "region-block:view", Sort: 66, Visible: true},
|
||||||
{ParentID: &roomsID, Title: "房间列表", Code: "room-list", Path: "/rooms", Icon: "room", PermissionCode: "room:view", Sort: 65, Visible: true},
|
{ParentID: &roomsID, Title: "房间列表", Code: "room-list", Path: "/rooms", Icon: "room", PermissionCode: "room:view", Sort: 65, Visible: true},
|
||||||
{ParentID: &roomsID, Title: "房间置顶", Code: "room-pins", Path: "/rooms/pins", Icon: "push_pin", PermissionCode: "room-pin:view", Sort: 66, Visible: true},
|
{ParentID: &roomsID, Title: "房间置顶", Code: "room-pins", Path: "/rooms/pins", Icon: "push_pin", PermissionCode: "room-pin:view", Sort: 66, Visible: true},
|
||||||
{ParentID: &roomsID, Title: "房间配置", Code: "room-config", Path: "/rooms/config", Icon: "settings", PermissionCode: "room-config:view", Sort: 67, Visible: true},
|
{ParentID: &roomsID, Title: "房间配置", Code: "room-config", Path: "/rooms/config", Icon: "settings", PermissionCode: "room-config:view", Sort: 67, Visible: true},
|
||||||
@ -290,7 +297,8 @@ func (s *Store) seedMenus() error {
|
|||||||
{ParentID: &operationsID, Title: "全服通知", Code: "operation-full-server-notice", Path: "/operations/full-server-notices", Icon: "campaign", PermissionCode: "full-server-notice:view", Sort: 75, Visible: true},
|
{ParentID: &operationsID, Title: "全服通知", Code: "operation-full-server-notice", Path: "/operations/full-server-notices", Icon: "campaign", PermissionCode: "full-server-notice:view", Sort: 75, Visible: true},
|
||||||
{ParentID: &paymentID, Title: "账单列表", Code: "payment-bill-list", Path: "/payment/bills", Icon: "receipt", PermissionCode: "payment-bill:view", Sort: 68, Visible: true},
|
{ParentID: &paymentID, Title: "账单列表", Code: "payment-bill-list", Path: "/payment/bills", Icon: "receipt", PermissionCode: "payment-bill:view", Sort: 68, Visible: true},
|
||||||
{ParentID: &paymentID, Title: "三方支付", Code: "payment-third-party", Path: "/payment/third-party", Icon: "wallet", PermissionCode: "payment-third-party:view", Sort: 69, Visible: true},
|
{ParentID: &paymentID, Title: "三方支付", Code: "payment-third-party", Path: "/payment/third-party", Icon: "wallet", PermissionCode: "payment-third-party:view", Sort: 69, Visible: true},
|
||||||
{ParentID: &paymentID, Title: "支付内购商品", Code: "payment-recharge-products", Path: "/payment/recharge-products", Icon: "wallet", PermissionCode: "payment-product: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: &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},
|
||||||
@ -299,10 +307,11 @@ func (s *Store) seedMenus() error {
|
|||||||
{ParentID: &activityID, Title: "用户榜单", Code: "user-leaderboard", Path: "/activities/user-leaderboards", Icon: "leaderboard", PermissionCode: "user-leaderboard:view", Sort: 74, Visible: true},
|
{ParentID: &activityID, Title: "用户榜单", Code: "user-leaderboard", Path: "/activities/user-leaderboards", Icon: "leaderboard", PermissionCode: "user-leaderboard:view", Sort: 74, Visible: true},
|
||||||
{ParentID: &activityID, Title: "红包配置", Code: "red-packet", Path: "/activities/red-packets", Icon: "redeem", PermissionCode: "red-packet:view", Sort: 75, Visible: true},
|
{ParentID: &activityID, Title: "红包配置", Code: "red-packet", Path: "/activities/red-packets", Icon: "redeem", PermissionCode: "red-packet:view", Sort: 75, Visible: true},
|
||||||
{ParentID: &activityID, Title: "CP配置", Code: "cp-config", Path: "/activities/cp-config", Icon: "favorite", PermissionCode: "cp-config:view", Sort: 76, Visible: true},
|
{ParentID: &activityID, Title: "CP配置", Code: "cp-config", Path: "/activities/cp-config", Icon: "favorite", PermissionCode: "cp-config:view", Sort: 76, Visible: true},
|
||||||
{ParentID: &activityID, Title: "VIP配置", Code: "vip-config", Path: "/activities/vip-config", Icon: "workspace_premium", PermissionCode: "vip-config:view", Sort: 77, Visible: true},
|
{ParentID: &activityID, Title: "CP排行活动", Code: "cp-weekly-rank", Path: "/activities/cp-weekly-rank", Icon: "leaderboard", PermissionCode: "cp-weekly-rank:view", Sort: 77, Visible: true},
|
||||||
{ParentID: &activityID, Title: "周星配置", Code: "weekly-star", Path: "/activities/weekly-star", Icon: "star", PermissionCode: "weekly-star:view", Sort: 78, Visible: true},
|
{ParentID: &activityID, Title: "VIP配置", Code: "vip-config", Path: "/activities/vip-config", Icon: "workspace_premium", PermissionCode: "vip-config:view", Sort: 78, Visible: true},
|
||||||
{ParentID: &activityID, Title: "代理开业活动", Code: "agency-opening", Path: "/activities/agency-opening", Icon: "workspace_premium", PermissionCode: "agency-opening:view", Sort: 79, Visible: true},
|
{ParentID: &activityID, Title: "周星配置", Code: "weekly-star", Path: "/activities/weekly-star", Icon: "star", PermissionCode: "weekly-star:view", Sort: 79, Visible: true},
|
||||||
{ParentID: &activityID, Title: "转盘抽奖", Code: "wheel", Path: "/activities/wheel", Icon: "casino", PermissionCode: "wheel:view", Sort: 80, Visible: true},
|
{ParentID: &activityID, Title: "代理开业活动", Code: "agency-opening", Path: "/activities/agency-opening", Icon: "workspace_premium", PermissionCode: "agency-opening:view", Sort: 80, Visible: true},
|
||||||
|
{ParentID: &activityID, Title: "转盘抽奖", Code: "wheel", Path: "/activities/wheel", Icon: "casino", PermissionCode: "wheel:view", Sort: 81, Visible: true},
|
||||||
{ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true},
|
{ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true},
|
||||||
{ParentID: &gameID, Title: "自研游戏", Code: "self-games", Path: "/games/self-games", Icon: "settings", PermissionCode: "game:view", Sort: 80, Visible: true},
|
{ParentID: &gameID, Title: "自研游戏", Code: "self-games", Path: "/games/self-games", Icon: "settings", PermissionCode: "game:view", Sort: 80, Visible: true},
|
||||||
{ParentID: &gameID, Title: "全站机器人", Code: "game-robots", Path: "/games/robots", Icon: "team", PermissionCode: "game:view", Sort: 90, Visible: true},
|
{ParentID: &gameID, Title: "全站机器人", Code: "game-robots", Path: "/games/robots", Icon: "team", PermissionCode: "game:view", Sort: 90, Visible: true},
|
||||||
@ -527,6 +536,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"app-user:view", "app-user:update", "app-user:status", "app-user:password",
|
"app-user:view", "app-user:update", "app-user:status", "app-user:password",
|
||||||
"level-config:view", "level-config:update",
|
"level-config:view", "level-config:update",
|
||||||
"pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant",
|
"pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant",
|
||||||
|
"risk-config:view", "risk-config:update",
|
||||||
"region-block:view", "region-block:update",
|
"region-block:view", "region-block:update",
|
||||||
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-whitelist:view", "room-whitelist:update", "room-robot:view", "room-robot:create", "room-robot:update",
|
"room:view", "room:update", "room:delete", "room-pin:view", "room-pin:create", "room-pin:cancel", "room-config:view", "room-config:update", "room-whitelist:view", "room-whitelist:update", "room-robot:view", "room-robot:create", "room-robot:update",
|
||||||
"app-config:view", "app-config:update",
|
"app-config:view", "app-config:update",
|
||||||
@ -534,7 +544,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"resource:view", "resource:create", "resource:update",
|
"resource:view", "resource:create", "resource:update",
|
||||||
"resource-shop:view", "resource-shop:update",
|
"resource-shop:view", "resource-shop:update",
|
||||||
"resource-group:view", "resource-group:create", "resource-group:update",
|
"resource-group:view", "resource-group:create", "resource-group:update",
|
||||||
"resource-grant:view", "resource-grant:create",
|
"resource-grant:view", "resource-grant:create", "resource-grant:revoke",
|
||||||
"gift:view", "gift:create", "gift:update", "gift:status", "gift:delete",
|
"gift:view", "gift:create", "gift:update", "gift:status", "gift:delete",
|
||||||
"emoji-pack:view", "emoji-pack:create",
|
"emoji-pack:view", "emoji-pack:create",
|
||||||
"country:view", "country:create", "country:update", "country:status",
|
"country:view", "country:create", "country:update", "country:status",
|
||||||
@ -543,7 +553,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"agency:view", "agency:create", "agency:status", "agency:delete",
|
"agency:view", "agency:create", "agency:status", "agency:delete",
|
||||||
"bd:view", "bd:create", "bd:update",
|
"bd:view", "bd:create", "bd:update",
|
||||||
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
|
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
|
||||||
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
|
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-temporary-link:view", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
|
||||||
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
|
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
|
||||||
"game:view", "game:create", "game:update", "game:status", "game:delete",
|
"game:view", "game:create", "game:update", "game:status", "game:delete",
|
||||||
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
|
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
|
||||||
@ -551,7 +561,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"seven-day-checkin:view", "seven-day-checkin:update",
|
"seven-day-checkin:view", "seven-day-checkin:update",
|
||||||
"room-rocket:view", "room-rocket:update",
|
"room-rocket:view", "room-rocket:update",
|
||||||
"red-packet:view", "red-packet:update",
|
"red-packet:view", "red-packet:update",
|
||||||
"cp-config:view", "cp-config:update",
|
"cp-config:view", "cp-config:update", "cp-weekly-rank:view", "cp-weekly-rank:update",
|
||||||
"vip-config:view", "vip-config:update", "vip-config:grant",
|
"vip-config:view", "vip-config:update", "vip-config:grant",
|
||||||
"weekly-star:view", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
"weekly-star:view", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
||||||
"agency-opening:view", "agency-opening:create", "agency-opening:update",
|
"agency-opening:view", "agency-opening:create", "agency-opening:update",
|
||||||
@ -560,7 +570,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"upload:create",
|
"upload:create",
|
||||||
}
|
}
|
||||||
case "auditor":
|
case "auditor":
|
||||||
return []string{"overview:view", "log:view", "user:view", "app-user:view", "level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view", "role:view", "permission:view", "job:view"}
|
return []string{"overview:view", "log:view", "user:view", "app-user:view", "level-config:view", "pretty-id:view", "risk-config:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-temporary-link:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "cp-weekly-rank:view", "vip-config:view", "weekly-star:view", "agency-opening:view", "role:view", "permission:view", "job:view"}
|
||||||
case "readonly":
|
case "readonly":
|
||||||
return []string{
|
return []string{
|
||||||
"overview:view",
|
"overview:view",
|
||||||
@ -568,6 +578,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"app-user:view",
|
"app-user:view",
|
||||||
"level-config:view",
|
"level-config:view",
|
||||||
"pretty-id:view",
|
"pretty-id:view",
|
||||||
|
"risk-config:view",
|
||||||
"region-block:view",
|
"region-block:view",
|
||||||
"room:view",
|
"room:view",
|
||||||
"room-pin:view",
|
"room-pin:view",
|
||||||
@ -601,6 +612,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"wheel:view",
|
"wheel:view",
|
||||||
"payment-bill:view",
|
"payment-bill:view",
|
||||||
"payment-third-party:view",
|
"payment-third-party:view",
|
||||||
|
"payment-temporary-link:view",
|
||||||
"payment-product:view",
|
"payment-product:view",
|
||||||
"game:view",
|
"game:view",
|
||||||
"daily-task:view",
|
"daily-task:view",
|
||||||
@ -609,6 +621,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
"room-rocket:view",
|
"room-rocket:view",
|
||||||
"red-packet:view",
|
"red-packet:view",
|
||||||
"cp-config:view",
|
"cp-config:view",
|
||||||
|
"cp-weekly-rank:view",
|
||||||
"vip-config:view",
|
"vip-config:view",
|
||||||
"weekly-star:view",
|
"weekly-star:view",
|
||||||
"agency-opening:view",
|
"agency-opening:view",
|
||||||
@ -633,11 +646,12 @@ func defaultRolePermissionMigrationCodes(code string) []string {
|
|||||||
"app-version:view", "app-version:create", "app-version:update", "app-version:delete",
|
"app-version:view", "app-version:create", "app-version:update", "app-version:delete",
|
||||||
"level-config:view", "level-config:update",
|
"level-config:view", "level-config:update",
|
||||||
"pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant",
|
"pretty-id:view", "pretty-id:update", "pretty-id:generate", "pretty-id:grant",
|
||||||
|
"risk-config:view", "risk-config:update",
|
||||||
"region-block:view", "region-block:update",
|
"region-block:view", "region-block:update",
|
||||||
"resource:view", "resource:create", "resource:update",
|
"resource:view", "resource:create", "resource:update",
|
||||||
"resource-shop:view", "resource-shop:update",
|
"resource-shop:view", "resource-shop:update",
|
||||||
"resource-group:view", "resource-group:create", "resource-group:update",
|
"resource-group:view", "resource-group:create", "resource-group:update",
|
||||||
"resource-grant:view", "resource-grant:create",
|
"resource-grant:view", "resource-grant:create", "resource-grant:revoke",
|
||||||
"gift:view", "gift:create", "gift:update", "gift:status", "gift:delete",
|
"gift:view", "gift:create", "gift:update", "gift:status", "gift:delete",
|
||||||
"emoji-pack:view", "emoji-pack:create",
|
"emoji-pack:view", "emoji-pack:create",
|
||||||
"upload:create",
|
"upload:create",
|
||||||
@ -645,7 +659,7 @@ func defaultRolePermissionMigrationCodes(code string) []string {
|
|||||||
"region:view", "region:create", "region:update", "region:status",
|
"region:view", "region:create", "region:update", "region:status",
|
||||||
"host-agency-policy:view", "host-agency-policy:create", "host-agency-policy:update", "host-agency-policy:delete", "host-agency-policy:publish", "team-salary-policy:view", "team-salary-policy:create", "team-salary-policy:update", "team-salary-policy:delete", "host-salary-settlement:view", "host-salary-settlement:settle",
|
"host-agency-policy:view", "host-agency-policy:create", "host-agency-policy:update", "host-agency-policy:delete", "host-agency-policy:publish", "team-salary-policy:view", "team-salary-policy:create", "team-salary-policy:update", "team-salary-policy:delete", "host-salary-settlement:view", "host-salary-settlement:settle",
|
||||||
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
|
"coin-seller:view", "coin-seller:create", "coin-seller:update", "coin-seller:stock-credit", "coin-seller:exchange-rate",
|
||||||
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
|
"coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "coin-adjustment:create", "report:view", "gift-diamond:view", "gift-diamond:update", "full-server-notice:view", "full-server-notice:send", "payment-bill:view", "payment-third-party:view", "payment-third-party:update", "payment-temporary-link:view", "payment-product:view", "payment-product:create", "payment-product:update", "payment-product:delete",
|
||||||
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
|
"lucky-gift:view", "lucky-gift:update", "wheel:view", "wheel:update",
|
||||||
"game:view", "game:create", "game:update", "game:status", "game:delete",
|
"game:view", "game:create", "game:update", "game:status", "game:delete",
|
||||||
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
|
"daily-task:view", "daily-task:create", "daily-task:update", "daily-task:status",
|
||||||
@ -653,13 +667,13 @@ func defaultRolePermissionMigrationCodes(code string) []string {
|
|||||||
"seven-day-checkin:view", "seven-day-checkin:update",
|
"seven-day-checkin:view", "seven-day-checkin:update",
|
||||||
"room-rocket:view", "room-rocket:update",
|
"room-rocket:view", "room-rocket:update",
|
||||||
"red-packet:view", "red-packet:update",
|
"red-packet:view", "red-packet:update",
|
||||||
"cp-config:view", "cp-config:update",
|
"cp-config:view", "cp-config:update", "cp-weekly-rank:view", "cp-weekly-rank:update",
|
||||||
"vip-config:view", "vip-config:update", "vip-config:grant",
|
"vip-config:view", "vip-config:update", "vip-config:grant",
|
||||||
"weekly-star:view", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
"weekly-star:view", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
||||||
"agency-opening:view", "agency-opening:create", "agency-opening:update",
|
"agency-opening:view", "agency-opening:create", "agency-opening:update",
|
||||||
}
|
}
|
||||||
case "auditor", "readonly":
|
case "auditor", "readonly":
|
||||||
return []string{"level-config:view", "pretty-id:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-seller:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "vip-config:view", "weekly-star:view", "agency-opening:view"}
|
return []string{"level-config:view", "pretty-id:view", "risk-config:view", "region-block:view", "room:view", "room-pin:view", "room-config:view", "room-whitelist:view", "room-robot:view", "app-config:view", "app-version:view", "resource:view", "resource-shop:view", "resource-group:view", "resource-grant:view", "gift:view", "emoji-pack:view", "host-agency-policy:view", "team-salary-policy:view", "host-salary-settlement:view", "coin-seller:view", "coin-ledger:view", "coin-seller-ledger:view", "coin-adjustment:view", "report:view", "gift-diamond:view", "full-server-notice:view", "lucky-gift:view", "wheel:view", "payment-bill:view", "payment-third-party:view", "payment-temporary-link:view", "payment-product:view", "game:view", "daily-task:view", "achievement:view", "seven-day-checkin:view", "room-rocket:view", "red-packet:view", "cp-config:view", "cp-weekly-rank:view", "vip-config:view", "weekly-star:view", "agency-opening:view"}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"hyapp-admin-server/internal/modules/coinledger"
|
"hyapp-admin-server/internal/modules/coinledger"
|
||||||
"hyapp-admin-server/internal/modules/countryregion"
|
"hyapp-admin-server/internal/modules/countryregion"
|
||||||
"hyapp-admin-server/internal/modules/cprelation"
|
"hyapp-admin-server/internal/modules/cprelation"
|
||||||
|
"hyapp-admin-server/internal/modules/cpweeklyrank"
|
||||||
"hyapp-admin-server/internal/modules/cumulativerechargereward"
|
"hyapp-admin-server/internal/modules/cumulativerechargereward"
|
||||||
"hyapp-admin-server/internal/modules/dailytask"
|
"hyapp-admin-server/internal/modules/dailytask"
|
||||||
"hyapp-admin-server/internal/modules/dashboard"
|
"hyapp-admin-server/internal/modules/dashboard"
|
||||||
@ -38,6 +39,7 @@ import (
|
|||||||
"hyapp-admin-server/internal/modules/registrationreward"
|
"hyapp-admin-server/internal/modules/registrationreward"
|
||||||
reportmodule "hyapp-admin-server/internal/modules/report"
|
reportmodule "hyapp-admin-server/internal/modules/report"
|
||||||
resourcemodule "hyapp-admin-server/internal/modules/resource"
|
resourcemodule "hyapp-admin-server/internal/modules/resource"
|
||||||
|
"hyapp-admin-server/internal/modules/riskconfig"
|
||||||
"hyapp-admin-server/internal/modules/roomadmin"
|
"hyapp-admin-server/internal/modules/roomadmin"
|
||||||
"hyapp-admin-server/internal/modules/roomrocket"
|
"hyapp-admin-server/internal/modules/roomrocket"
|
||||||
"hyapp-admin-server/internal/modules/roomturnoverreward"
|
"hyapp-admin-server/internal/modules/roomturnoverreward"
|
||||||
@ -68,6 +70,7 @@ type Handlers struct {
|
|||||||
CoinLedger *coinledger.Handler
|
CoinLedger *coinledger.Handler
|
||||||
CountryRegion *countryregion.Handler
|
CountryRegion *countryregion.Handler
|
||||||
CPRelation *cprelation.Handler
|
CPRelation *cprelation.Handler
|
||||||
|
CPWeeklyRank *cpweeklyrank.Handler
|
||||||
CumulativeRecharge *cumulativerechargereward.Handler
|
CumulativeRecharge *cumulativerechargereward.Handler
|
||||||
DailyTask *dailytask.Handler
|
DailyTask *dailytask.Handler
|
||||||
Dashboard *dashboard.Handler
|
Dashboard *dashboard.Handler
|
||||||
@ -92,6 +95,7 @@ type Handlers struct {
|
|||||||
RegistrationReward *registrationreward.Handler
|
RegistrationReward *registrationreward.Handler
|
||||||
RegionBlock *regionblock.Handler
|
RegionBlock *regionblock.Handler
|
||||||
Resource *resourcemodule.Handler
|
Resource *resourcemodule.Handler
|
||||||
|
RiskConfig *riskconfig.Handler
|
||||||
RoomAdmin *roomadmin.Handler
|
RoomAdmin *roomadmin.Handler
|
||||||
RoomRocket *roomrocket.Handler
|
RoomRocket *roomrocket.Handler
|
||||||
RoomTurnoverReward *roomturnoverreward.Handler
|
RoomTurnoverReward *roomturnoverreward.Handler
|
||||||
@ -132,12 +136,14 @@ func New(cfg config.Config, auth *service.AuthService, h Handlers) *gin.Engine {
|
|||||||
appconfig.RegisterRoutes(protected, h.AppConfig)
|
appconfig.RegisterRoutes(protected, h.AppConfig)
|
||||||
countryregion.RegisterRoutes(protected, h.CountryRegion)
|
countryregion.RegisterRoutes(protected, h.CountryRegion)
|
||||||
cprelation.RegisterRoutes(protected, h.CPRelation)
|
cprelation.RegisterRoutes(protected, h.CPRelation)
|
||||||
|
cpweeklyrank.RegisterRoutes(protected, h.CPWeeklyRank)
|
||||||
cumulativerechargereward.RegisterRoutes(protected, h.CumulativeRecharge)
|
cumulativerechargereward.RegisterRoutes(protected, h.CumulativeRecharge)
|
||||||
dailytask.RegisterRoutes(protected, h.DailyTask)
|
dailytask.RegisterRoutes(protected, h.DailyTask)
|
||||||
firstrechargereward.RegisterRoutes(protected, h.FirstRechargeReward)
|
firstrechargereward.RegisterRoutes(protected, h.FirstRechargeReward)
|
||||||
fullservernotice.RegisterRoutes(protected, h.FullServerNotice)
|
fullservernotice.RegisterRoutes(protected, h.FullServerNotice)
|
||||||
registrationreward.RegisterRoutes(protected, h.RegistrationReward)
|
registrationreward.RegisterRoutes(protected, h.RegistrationReward)
|
||||||
regionblock.RegisterRoutes(protected, h.RegionBlock)
|
regionblock.RegisterRoutes(protected, h.RegionBlock)
|
||||||
|
riskconfig.RegisterRoutes(protected, h.RiskConfig)
|
||||||
gamemanagement.RegisterRoutes(protected, h.Game)
|
gamemanagement.RegisterRoutes(protected, h.Game)
|
||||||
giftdiamond.RegisterRoutes(protected, h.GiftDiamond)
|
giftdiamond.RegisterRoutes(protected, h.GiftDiamond)
|
||||||
roomadmin.RegisterRoutes(protected, h.RoomAdmin)
|
roomadmin.RegisterRoutes(protected, h.RoomAdmin)
|
||||||
|
|||||||
54
server/admin/migrations/058_room_pin_navigation.sql
Normal file
54
server/admin/migrations/058_room_pin_navigation.sql
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 房间置顶是房间管理里的运营入口;生产环境关闭 bootstrap 时,只能依赖 migration 补齐菜单和权限事实。
|
||||||
|
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
|
||||||
|
('房间置顶查看', 'room-pin:view', 'menu', '允许查看房间置顶列表', @now_ms, @now_ms),
|
||||||
|
('房间置顶创建', 'room-pin:create', 'button', '允许新增或恢复房间置顶', @now_ms, @now_ms),
|
||||||
|
('房间置顶取消', 'room-pin:cancel', 'button', '允许取消房间置顶', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
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, '房间置顶', 'room-pins', '/rooms/pins', 'push_pin', 'room-pin:view', 66, TRUE, @now_ms, @now_ms
|
||||||
|
FROM admin_menus parent
|
||||||
|
WHERE parent.code = 'rooms'
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- 置顶入口排在房间配置、白名单和机器人房间前面;显式排序避免多次修复或历史状态不同造成排序漂移。
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = CASE code
|
||||||
|
WHEN 'room-config' THEN 67
|
||||||
|
WHEN 'room-whitelist' THEN 68
|
||||||
|
WHEN 'room-robots' THEN 69
|
||||||
|
ELSE sort
|
||||||
|
END,
|
||||||
|
updated_at_ms = @now_ms
|
||||||
|
WHERE code IN ('room-config', 'room-whitelist', 'room-robots');
|
||||||
|
|
||||||
|
-- 平台和运营角色可以执行置顶变更;只读和审计角色只能看到列表,避免误给写权限。
|
||||||
|
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')
|
||||||
|
AND admin_permission.code IN ('room-pin:view', 'room-pin:create', 'room-pin:cancel');
|
||||||
|
|
||||||
|
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 ('auditor', 'readonly')
|
||||||
|
AND admin_permission.code = 'room-pin:view';
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 资源赠送撤销会触发钱包扣回和权益失效,必须独立成按钮权限,不能复用创建权限。
|
||||||
|
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
|
||||||
|
('资源赠送撤销', 'resource-grant:revoke', 'button', '允许撤销成功的资源组赠送', @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 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')
|
||||||
|
AND admin_permission.code = 'resource-grant:revoke';
|
||||||
46
server/admin/migrations/060_risk_config_navigation.sql
Normal file
46
server/admin/migrations/060_risk_config_navigation.sql
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 注册风控配置由 user-service 执行,后台只负责维护菜单、权限和跨服务配置入口。
|
||||||
|
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
|
||||||
|
('风控配置查看', 'risk-config:view', 'menu', '', @now_ms, @now_ms),
|
||||||
|
('风控配置更新', 'risk-config:update', 'button', '', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
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, '风控管理', 'app-user-risk-config', '/app/users/risk-config', 'shield', 'risk-config:view', 65, TRUE, @now_ms, @now_ms
|
||||||
|
FROM admin_menus parent
|
||||||
|
WHERE parent.code = 'app-users'
|
||||||
|
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;
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 66, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'app-user-region-blocks'
|
||||||
|
AND sort < 66;
|
||||||
|
|
||||||
|
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')
|
||||||
|
AND admin_permission.code IN ('risk-config:view', 'risk-config:update');
|
||||||
|
|
||||||
|
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 ('auditor', 'readonly')
|
||||||
|
AND admin_permission.code = 'risk-config:view';
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- 三方临时支付链接只用于内部追踪临时支付订单;权限独立于三方支付配置更新权限,避免只读运营误改渠道配置。
|
||||||
|
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
|
||||||
|
('三方临时支付链接查看', 'payment-temporary-link:view', 'menu', '', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
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, '三方临时支付链接', 'payment-temporary-links', '/payment/temporary-links', 'receipt', 'payment-temporary-link:view', 70, TRUE, @now_ms, @now_ms
|
||||||
|
FROM admin_menus parent
|
||||||
|
WHERE parent.code = 'payment'
|
||||||
|
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;
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 71, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'payment-recharge-products';
|
||||||
|
|
||||||
|
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', 'auditor', 'readonly')
|
||||||
|
AND admin_permission.code = 'payment-temporary-link:view';
|
||||||
57
server/admin/migrations/062_cp_weekly_rank_navigation.sql
Normal file
57
server/admin/migrations/062_cp_weekly_rank_navigation.sql
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CP排行活动对应 H5 的 cp-weekly-rank 活动读接口;后台只维护启停、Top 奖励资源组和查看发奖事实。
|
||||||
|
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
|
||||||
|
('CP 排行活动查看', 'cp-weekly-rank:view', 'menu', '', @now_ms, @now_ms),
|
||||||
|
('CP 排行活动更新', 'cp-weekly-rank:update', 'button', '', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
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, 'CP排行活动', 'cp-weekly-rank', '/activities/cp-weekly-rank', 'leaderboard', 'cp-weekly-rank:view', 77, 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;
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 78, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'vip-config';
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 79, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'weekly-star';
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 80, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'agency-opening';
|
||||||
|
|
||||||
|
UPDATE admin_menus
|
||||||
|
SET sort = 81, updated_at_ms = @now_ms
|
||||||
|
WHERE code = 'wheel';
|
||||||
|
|
||||||
|
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')
|
||||||
|
AND admin_permission.code IN ('cp-weekly-rank:view', 'cp-weekly-rank:update');
|
||||||
|
|
||||||
|
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 ('auditor', 'readonly')
|
||||||
|
AND admin_permission.code = 'cp-weekly-rank:view';
|
||||||
@ -24,6 +24,13 @@ red_packet_broadcast_worker:
|
|||||||
enabled: true
|
enabled: true
|
||||||
task_event_worker:
|
task_event_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
message_action_confirm_worker:
|
||||||
|
enabled: true
|
||||||
|
outbox_poll_interval: "1s"
|
||||||
|
outbox_batch_size: 100
|
||||||
|
outbox_lock_ttl: "30s"
|
||||||
|
outbox_max_retry: 8
|
||||||
|
publish_timeout: "5s"
|
||||||
lucky_gift_worker:
|
lucky_gift_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
worker_poll_interval: "1s"
|
worker_poll_interval: "1s"
|
||||||
@ -82,7 +89,14 @@ rocketmq:
|
|||||||
consumer_group: "hyapp-activity-user-region-broadcast"
|
consumer_group: "hyapp-activity-user-region-broadcast"
|
||||||
task_consumer_group: "hyapp-activity-task-user-outbox"
|
task_consumer_group: "hyapp-activity-task-user-outbox"
|
||||||
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"
|
||||||
consumer_max_reconsume_times: 16
|
consumer_max_reconsume_times: 16
|
||||||
|
message_action_outbox:
|
||||||
|
enabled: true
|
||||||
|
topic: "hyapp_message_action_outbox"
|
||||||
|
producer_group: "hyapp-activity-message-action-outbox"
|
||||||
|
send_timeout: "5s"
|
||||||
|
retry: 2
|
||||||
consumer:
|
consumer:
|
||||||
room_outbox_poll_interval_ms: 1000
|
room_outbox_poll_interval_ms: 1000
|
||||||
batch_size: 100
|
batch_size: 100
|
||||||
|
|||||||
@ -24,6 +24,13 @@ red_packet_broadcast_worker:
|
|||||||
enabled: true
|
enabled: true
|
||||||
task_event_worker:
|
task_event_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
message_action_confirm_worker:
|
||||||
|
enabled: true
|
||||||
|
outbox_poll_interval: "1s"
|
||||||
|
outbox_batch_size: 100
|
||||||
|
outbox_lock_ttl: "30s"
|
||||||
|
outbox_max_retry: 8
|
||||||
|
publish_timeout: "5s"
|
||||||
lucky_gift_worker:
|
lucky_gift_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
worker_poll_interval: "1s"
|
worker_poll_interval: "1s"
|
||||||
@ -82,7 +89,14 @@ rocketmq:
|
|||||||
consumer_group: "hyapp-activity-user-region-broadcast"
|
consumer_group: "hyapp-activity-user-region-broadcast"
|
||||||
task_consumer_group: "hyapp-activity-task-user-outbox"
|
task_consumer_group: "hyapp-activity-task-user-outbox"
|
||||||
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"
|
||||||
consumer_max_reconsume_times: 16
|
consumer_max_reconsume_times: 16
|
||||||
|
message_action_outbox:
|
||||||
|
enabled: true
|
||||||
|
topic: "hyapp_message_action_outbox"
|
||||||
|
producer_group: "hyapp-activity-message-action-outbox"
|
||||||
|
send_timeout: "5s"
|
||||||
|
retry: 2
|
||||||
consumer:
|
consumer:
|
||||||
room_outbox_poll_interval_ms: 1000
|
room_outbox_poll_interval_ms: 1000
|
||||||
batch_size: 100
|
batch_size: 100
|
||||||
|
|||||||
@ -24,6 +24,13 @@ red_packet_broadcast_worker:
|
|||||||
enabled: true
|
enabled: true
|
||||||
task_event_worker:
|
task_event_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
message_action_confirm_worker:
|
||||||
|
enabled: true
|
||||||
|
outbox_poll_interval: "1s"
|
||||||
|
outbox_batch_size: 100
|
||||||
|
outbox_lock_ttl: "30s"
|
||||||
|
outbox_max_retry: 8
|
||||||
|
publish_timeout: "5s"
|
||||||
lucky_gift_worker:
|
lucky_gift_worker:
|
||||||
enabled: true
|
enabled: true
|
||||||
worker_poll_interval: "1s"
|
worker_poll_interval: "1s"
|
||||||
@ -82,7 +89,14 @@ rocketmq:
|
|||||||
consumer_group: "hyapp-activity-user-region-broadcast"
|
consumer_group: "hyapp-activity-user-region-broadcast"
|
||||||
task_consumer_group: "hyapp-activity-task-user-outbox"
|
task_consumer_group: "hyapp-activity-task-user-outbox"
|
||||||
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"
|
||||||
consumer_max_reconsume_times: 16
|
consumer_max_reconsume_times: 16
|
||||||
|
message_action_outbox:
|
||||||
|
enabled: true
|
||||||
|
topic: "hyapp_message_action_outbox"
|
||||||
|
producer_group: "hyapp-activity-message-action-outbox"
|
||||||
|
send_timeout: "5s"
|
||||||
|
retry: 2
|
||||||
consumer:
|
consumer:
|
||||||
room_outbox_poll_interval_ms: 1000
|
room_outbox_poll_interval_ms: 1000
|
||||||
batch_size: 100
|
batch_size: 100
|
||||||
|
|||||||
@ -1201,6 +1201,55 @@ CREATE TABLE IF NOT EXISTS message_fanout_jobs (
|
|||||||
KEY idx_message_fanout_status (app_code, status, next_run_at_ms, updated_at_ms)
|
KEY idx_message_fanout_status (app_code, status, next_run_at_ms, updated_at_ms)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息分发任务表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息分发任务表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_action_confirms (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT 'App 租户编码',
|
||||||
|
confirm_id VARCHAR(96) NOT NULL COMMENT 'App 操作确认 ID',
|
||||||
|
source_name VARCHAR(64) NOT NULL COMMENT '来源事实名,例如 user_outbox',
|
||||||
|
source_event_id VARCHAR(128) NOT NULL COMMENT '来源 outbox event_id',
|
||||||
|
business_type VARCHAR(64) NOT NULL COMMENT '业务类型,例如 role_invitation',
|
||||||
|
business_subtype VARCHAR(64) NOT NULL COMMENT '业务子类型,例如 host/agency/bd',
|
||||||
|
business_id VARCHAR(128) NOT NULL COMMENT '业务 owner 的事实 ID',
|
||||||
|
sender_user_id BIGINT NOT NULL COMMENT 'IM 发送方用户 ID',
|
||||||
|
receiver_user_id BIGINT NOT NULL COMMENT 'IM 接收方用户 ID',
|
||||||
|
message_text VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'IM 展示文案快照',
|
||||||
|
status VARCHAR(32) NOT NULL COMMENT 'pending/processing/accepted/rejected/expired/canceled/failed',
|
||||||
|
action VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终态动作 accept/reject',
|
||||||
|
command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '处理动作 command_id',
|
||||||
|
processed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理完成 UTC epoch ms',
|
||||||
|
locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'processing 锁持有者',
|
||||||
|
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'processing 锁过期 UTC epoch ms',
|
||||||
|
error_message VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次处理失败原因',
|
||||||
|
metadata_json JSON 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, confirm_id),
|
||||||
|
UNIQUE KEY uk_message_action_source (app_code, source_name, source_event_id),
|
||||||
|
KEY idx_message_action_receiver_status (app_code, receiver_user_id, status, created_at_ms, confirm_id),
|
||||||
|
KEY idx_message_action_conversation (app_code, receiver_user_id, sender_user_id, created_at_ms, confirm_id),
|
||||||
|
KEY idx_message_action_lock (app_code, status, lock_until_ms, created_at_ms)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App IM 操作确认消息状态';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_action_outbox (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT 'App 租户编码',
|
||||||
|
event_id VARCHAR(128) NOT NULL COMMENT '确认消息 outbox 事件 ID',
|
||||||
|
confirm_id VARCHAR(96) NOT NULL COMMENT '关联 confirm_id',
|
||||||
|
event_type VARCHAR(96) NOT NULL COMMENT '事件类型,例如 ConfirmMessageCreated',
|
||||||
|
status VARCHAR(32) NOT NULL COMMENT 'pending/running/retryable/delivered/failed',
|
||||||
|
worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'MQ 投递 worker ID',
|
||||||
|
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'MQ 投递锁过期 UTC epoch ms',
|
||||||
|
retry_count INT NOT NULL DEFAULT 0 COMMENT '投递重试次数',
|
||||||
|
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次投递 UTC epoch ms',
|
||||||
|
last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次投递失败原因',
|
||||||
|
payload_json JSON NOT NULL COMMENT '投递给 notice-service 的确认 IM 快照',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '创建 UTC epoch ms',
|
||||||
|
updated_at_ms BIGINT NOT NULL COMMENT '更新 UTC epoch ms',
|
||||||
|
PRIMARY KEY (app_code, event_id),
|
||||||
|
UNIQUE KEY uk_message_action_outbox_confirm (app_code, confirm_id, event_type),
|
||||||
|
KEY idx_message_action_outbox_status_created (app_code, status, next_retry_at_ms, created_at_ms, event_id),
|
||||||
|
KEY idx_message_action_outbox_lock (app_code, status, lock_until_ms, created_at_ms, event_id),
|
||||||
|
KEY idx_message_action_outbox_retention (app_code, status, updated_at_ms, event_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='确认消息 IM 投递 outbox';
|
||||||
|
|
||||||
-- 本地开发必须开箱即可验证幸运礼物 v2 链路;INSERT IGNORE 只补缺省奖池,不覆盖后台已发布版本。
|
-- 本地开发必须开箱即可验证幸运礼物 v2 链路;INSERT IGNORE 只补缺省奖池,不覆盖后台已发布版本。
|
||||||
SET @lucky_seed_now_ms := 1779259000000;
|
SET @lucky_seed_now_ms := 1779259000000;
|
||||||
INSERT IGNORE INTO lucky_gift_rule_versions (
|
INSERT IGNORE INTO lucky_gift_rule_versions (
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS message_action_confirms (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT 'App 租户编码',
|
||||||
|
confirm_id VARCHAR(96) NOT NULL COMMENT 'App 操作确认 ID',
|
||||||
|
source_name VARCHAR(64) NOT NULL COMMENT '来源事实名,例如 user_outbox',
|
||||||
|
source_event_id VARCHAR(128) NOT NULL COMMENT '来源 outbox event_id',
|
||||||
|
business_type VARCHAR(64) NOT NULL COMMENT '业务类型,例如 role_invitation',
|
||||||
|
business_subtype VARCHAR(64) NOT NULL COMMENT '业务子类型,例如 host/agency/bd',
|
||||||
|
business_id VARCHAR(128) NOT NULL COMMENT '业务 owner 的事实 ID',
|
||||||
|
sender_user_id BIGINT NOT NULL COMMENT 'IM 发送方用户 ID',
|
||||||
|
receiver_user_id BIGINT NOT NULL COMMENT 'IM 接收方用户 ID',
|
||||||
|
message_text VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'IM 展示文案快照',
|
||||||
|
status VARCHAR(32) NOT NULL COMMENT 'pending/processing/accepted/rejected/expired/canceled/failed',
|
||||||
|
action VARCHAR(32) NOT NULL DEFAULT '' COMMENT '终态动作 accept/reject',
|
||||||
|
command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '处理动作 command_id',
|
||||||
|
processed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理完成 UTC epoch ms',
|
||||||
|
locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'processing 锁持有者',
|
||||||
|
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'processing 锁过期 UTC epoch ms',
|
||||||
|
error_message VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次处理失败原因',
|
||||||
|
metadata_json JSON 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, confirm_id),
|
||||||
|
UNIQUE KEY uk_message_action_source (app_code, source_name, source_event_id),
|
||||||
|
KEY idx_message_action_receiver_status (app_code, receiver_user_id, status, created_at_ms, confirm_id),
|
||||||
|
KEY idx_message_action_conversation (app_code, receiver_user_id, sender_user_id, created_at_ms, confirm_id),
|
||||||
|
KEY idx_message_action_lock (app_code, status, lock_until_ms, created_at_ms)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App IM 操作确认消息状态';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS message_action_outbox (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT 'App 租户编码',
|
||||||
|
event_id VARCHAR(128) NOT NULL COMMENT '确认消息 outbox 事件 ID',
|
||||||
|
confirm_id VARCHAR(96) NOT NULL COMMENT '关联 confirm_id',
|
||||||
|
event_type VARCHAR(96) NOT NULL COMMENT '事件类型,例如 ConfirmMessageCreated',
|
||||||
|
status VARCHAR(32) NOT NULL COMMENT 'pending/running/retryable/delivered/failed',
|
||||||
|
worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'MQ 投递 worker ID',
|
||||||
|
lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'MQ 投递锁过期 UTC epoch ms',
|
||||||
|
retry_count INT NOT NULL DEFAULT 0 COMMENT '投递重试次数',
|
||||||
|
next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次投递 UTC epoch ms',
|
||||||
|
last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次投递失败原因',
|
||||||
|
payload_json JSON NOT NULL COMMENT '投递给 notice-service 的确认 IM 快照',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '创建 UTC epoch ms',
|
||||||
|
updated_at_ms BIGINT NOT NULL COMMENT '更新 UTC epoch ms',
|
||||||
|
PRIMARY KEY (app_code, event_id),
|
||||||
|
UNIQUE KEY uk_message_action_outbox_confirm (app_code, confirm_id, event_type),
|
||||||
|
KEY idx_message_action_outbox_status_created (app_code, status, next_retry_at_ms, created_at_ms, event_id),
|
||||||
|
KEY idx_message_action_outbox_lock (app_code, status, lock_until_ms, created_at_ms, event_id),
|
||||||
|
KEY idx_message_action_outbox_retention (app_code, status, updated_at_ms, event_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='确认消息 IM 投递 outbox';
|
||||||
153
services/activity-service/internal/app/action_confirm_mq.go
Normal file
153
services/activity-service/internal/app/action_confirm_mq.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/logx"
|
||||||
|
"hyapp/pkg/messagemq"
|
||||||
|
"hyapp/pkg/rocketmqx"
|
||||||
|
"hyapp/pkg/usermq"
|
||||||
|
"hyapp/services/activity-service/internal/config"
|
||||||
|
messagedomain "hyapp/services/activity-service/internal/domain/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
const userOutboxEventRoleInvitationCreated = "RoleInvitationCreated"
|
||||||
|
|
||||||
|
func newMessageActionUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
|
||||||
|
consumer, err := rocketmqx.NewConsumer(userOutboxMessageActionConsumerConfig(cfg.RocketMQ))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||||||
|
event, appCode, ok, err := roleInvitationConfirmEventFromUserMessage(message.Body)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, err = services.actionConfirm.ConsumeRoleInvitationCreated(appcode.WithContext(ctx, appCode), event)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
_ = consumer.Shutdown()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return consumer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type roleInvitationCreatedPayload struct {
|
||||||
|
InvitationID int64 `json:"invitation_id"`
|
||||||
|
InvitationType string `json:"invitation_type"`
|
||||||
|
InviterUserID int64 `json:"inviter_user_id"`
|
||||||
|
TargetUserID int64 `json:"target_user_id"`
|
||||||
|
AgencyName string `json:"agency_name"`
|
||||||
|
Inviter messagedomain.RoleInvitationUserSnapshot `json:"inviter"`
|
||||||
|
Target messagedomain.RoleInvitationUserSnapshot `json:"target"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
ParentBDUserID int64 `json:"parent_bd_user_id"`
|
||||||
|
ParentLeaderUserID int64 `json:"parent_leader_user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleInvitationConfirmEventFromUserMessage(body []byte) (messagedomain.RoleInvitationConfirmEvent, string, bool, error) {
|
||||||
|
message, err := usermq.DecodeUserOutboxMessage(body)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.RoleInvitationConfirmEvent{}, "", false, err
|
||||||
|
}
|
||||||
|
if message.EventType != userOutboxEventRoleInvitationCreated {
|
||||||
|
return messagedomain.RoleInvitationConfirmEvent{}, message.AppCode, false, nil
|
||||||
|
}
|
||||||
|
var payload roleInvitationCreatedPayload
|
||||||
|
if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil {
|
||||||
|
return messagedomain.RoleInvitationConfirmEvent{}, message.AppCode, true, err
|
||||||
|
}
|
||||||
|
return messagedomain.RoleInvitationConfirmEvent{
|
||||||
|
SourceEventID: message.EventID,
|
||||||
|
InvitationID: payload.InvitationID,
|
||||||
|
InvitationType: payload.InvitationType,
|
||||||
|
InviterUserID: payload.InviterUserID,
|
||||||
|
TargetUserID: payload.TargetUserID,
|
||||||
|
AgencyName: payload.AgencyName,
|
||||||
|
Inviter: payload.Inviter,
|
||||||
|
Target: payload.Target,
|
||||||
|
CreatedAtMS: firstPositiveMS(payload.CreatedAtMS, message.OccurredAtMS),
|
||||||
|
RawPayloadJSON: message.PayloadJSON,
|
||||||
|
ParentBDUserID: payload.ParentBDUserID,
|
||||||
|
ParentLeaderUserID: payload.ParentLeaderUserID,
|
||||||
|
}, message.AppCode, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) runMessageActionOutboxWorker(ctx context.Context) {
|
||||||
|
options := a.messageActionWorkerOptions
|
||||||
|
ticker := time.NewTicker(options.OutboxPollInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
processed, err := a.processMessageActionOutboxBatch(ctx)
|
||||||
|
if err != nil && ctx.Err() == nil {
|
||||||
|
logx.Error(ctx, "message_action_outbox_publish_failed", err, slog.String("node_id", a.workerNodeID))
|
||||||
|
}
|
||||||
|
if processed >= options.OutboxBatchSize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) processMessageActionOutboxBatch(ctx context.Context) (int, error) {
|
||||||
|
options := a.messageActionWorkerOptions
|
||||||
|
workerCtx := appcode.WithContext(ctx, appcode.Default)
|
||||||
|
records, err := a.actionConfirm.ClaimPendingOutbox(workerCtx, options.OutboxBatchSize, options.OutboxLockTTL)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
publishCtx, cancel := context.WithTimeout(context.Background(), options.PublishTimeout)
|
||||||
|
err := a.publishMessageActionOutboxRecord(publishCtx, record)
|
||||||
|
cancel()
|
||||||
|
recordCtx := appcode.WithContext(context.Background(), record.AppCode)
|
||||||
|
if err != nil {
|
||||||
|
nextRetryAtMS := time.Now().UTC().Add(options.OutboxPollInterval).UnixMilli()
|
||||||
|
if markErr := a.actionConfirm.MarkOutboxRetryable(recordCtx, record.EventID, err, nextRetryAtMS); markErr != nil {
|
||||||
|
return len(records), markErr
|
||||||
|
}
|
||||||
|
return len(records), err
|
||||||
|
}
|
||||||
|
if err := a.actionConfirm.MarkOutboxDelivered(recordCtx, record.EventID); err != nil {
|
||||||
|
return len(records), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(records), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) publishMessageActionOutboxRecord(ctx context.Context, record messagedomain.ActionConfirmOutbox) error {
|
||||||
|
body, err := messagemq.EncodeActionOutboxMessage(messagemq.ActionOutboxMessage{
|
||||||
|
AppCode: record.AppCode,
|
||||||
|
EventID: record.EventID,
|
||||||
|
EventType: record.EventType,
|
||||||
|
ConfirmID: record.ConfirmID,
|
||||||
|
PayloadJSON: record.PayloadJSON,
|
||||||
|
OccurredAtMS: record.CreatedAtMS,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.messageActionProducer.SendSync(ctx, rocketmqx.Message{
|
||||||
|
Topic: a.messageActionOutboxTopic,
|
||||||
|
Tag: messagemq.TagMessageActionOutboxEvent,
|
||||||
|
Keys: []string{record.EventID, record.ConfirmID},
|
||||||
|
Body: body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstPositiveMS(values ...int64) int64 {
|
||||||
|
for _, value := range values {
|
||||||
|
if value > 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Now().UTC().UnixMilli()
|
||||||
|
}
|
||||||
@ -21,32 +21,38 @@ import (
|
|||||||
firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge"
|
firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge"
|
||||||
inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity"
|
inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity"
|
||||||
luckygiftservice "hyapp/services/activity-service/internal/service/luckygift"
|
luckygiftservice "hyapp/services/activity-service/internal/service/luckygift"
|
||||||
|
messageservice "hyapp/services/activity-service/internal/service/message"
|
||||||
mysqlstorage "hyapp/services/activity-service/internal/storage/mysql"
|
mysqlstorage "hyapp/services/activity-service/internal/storage/mysql"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
luckyGift *luckygiftservice.Service
|
luckyGift *luckygiftservice.Service
|
||||||
firstRechargeReward *firstrechargeservice.Service
|
firstRechargeReward *firstrechargeservice.Service
|
||||||
cumulativeRecharge *cumulativerechargeservice.Service
|
cumulativeRecharge *cumulativerechargeservice.Service
|
||||||
inviteActivityReward *inviteactivityservice.Service
|
inviteActivityReward *inviteactivityservice.Service
|
||||||
broadcastWorkerEnabled bool
|
actionConfirm *messageservice.ActionConfirmService
|
||||||
luckyGiftWorkerEnabled bool
|
broadcastWorkerEnabled bool
|
||||||
workerNodeID string
|
luckyGiftWorkerEnabled bool
|
||||||
luckyGiftWorkerOptions luckygiftservice.WorkerOptions
|
messageActionWorkerEnabled bool
|
||||||
mqConsumers []*rocketmqx.Consumer
|
workerNodeID string
|
||||||
userConn *grpc.ClientConn
|
luckyGiftWorkerOptions luckygiftservice.WorkerOptions
|
||||||
walletConn *grpc.ClientConn
|
messageActionWorkerOptions config.MessageActionConfirmWorkerConfig
|
||||||
roomConn *grpc.ClientConn
|
messageActionOutboxTopic string
|
||||||
workers *serviceapp.BackgroundGroup
|
mqConsumers []*rocketmqx.Consumer
|
||||||
closeOnce sync.Once
|
messageActionProducer *rocketmqx.Producer
|
||||||
|
userConn *grpc.ClientConn
|
||||||
|
walletConn *grpc.ClientConn
|
||||||
|
roomConn *grpc.ClientConn
|
||||||
|
workers *serviceapp.BackgroundGroup
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// New 初始化 activity-service 应用。
|
// New 初始化 activity-service 应用。
|
||||||
@ -61,8 +67,10 @@ func New(cfg config.Config) (*App, error) {
|
|||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
var clients externalClients
|
var clients externalClients
|
||||||
var mqConsumers []*rocketmqx.Consumer
|
var mqConsumers []*rocketmqx.Consumer
|
||||||
|
var messageActionProducer *rocketmqx.Producer
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
shutdownConsumers(mqConsumers)
|
shutdownConsumers(mqConsumers)
|
||||||
|
servicemq.ShutdownProducers([]*rocketmqx.Producer{messageActionProducer})
|
||||||
clients.Close()
|
clients.Close()
|
||||||
if listener != nil {
|
if listener != nil {
|
||||||
_ = listener.Close()
|
_ = listener.Close()
|
||||||
@ -94,6 +102,13 @@ func New(cfg config.Config) (*App, error) {
|
|||||||
cleanup()
|
cleanup()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if cfg.RocketMQ.MessageActionOutbox.Enabled {
|
||||||
|
messageActionProducer, err = rocketmqx.NewProducer(messageActionProducerConfig(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()
|
||||||
@ -101,25 +116,30 @@ 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,
|
||||||
luckyGift: services.luckyGift,
|
luckyGift: services.luckyGift,
|
||||||
firstRechargeReward: services.firstRechargeReward,
|
firstRechargeReward: services.firstRechargeReward,
|
||||||
cumulativeRecharge: services.cumulativeRecharge,
|
cumulativeRecharge: services.cumulativeRecharge,
|
||||||
inviteActivityReward: services.inviteActivityReward,
|
inviteActivityReward: services.inviteActivityReward,
|
||||||
broadcastWorkerEnabled: cfg.Broadcast.Enabled,
|
actionConfirm: services.actionConfirm,
|
||||||
luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled,
|
broadcastWorkerEnabled: cfg.Broadcast.Enabled,
|
||||||
workerNodeID: cfg.NodeID,
|
luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled,
|
||||||
luckyGiftWorkerOptions: luckyGiftWorkerOptions(cfg.NodeID, cfg.LuckyGiftWorker),
|
messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled,
|
||||||
mqConsumers: mqConsumers,
|
workerNodeID: cfg.NodeID,
|
||||||
userConn: clients.userConn,
|
luckyGiftWorkerOptions: luckyGiftWorkerOptions(cfg.NodeID, cfg.LuckyGiftWorker),
|
||||||
walletConn: clients.walletConn,
|
messageActionWorkerOptions: cfg.MessageActionConfirmWorker,
|
||||||
roomConn: clients.roomConn,
|
messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic,
|
||||||
workers: serviceapp.NewBackground(context.Background()),
|
mqConsumers: mqConsumers,
|
||||||
|
messageActionProducer: messageActionProducer,
|
||||||
|
userConn: clients.userConn,
|
||||||
|
walletConn: clients.walletConn,
|
||||||
|
roomConn: clients.roomConn,
|
||||||
|
workers: serviceapp.NewBackground(context.Background()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +161,11 @@ func (a *App) Run() error {
|
|||||||
a.luckyGift.RunWorker(ctx, a.luckyGiftWorkerOptions)
|
a.luckyGift.RunWorker(ctx, a.luckyGiftWorkerOptions)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if a.messageActionWorkerEnabled && a.actionConfirm != nil && a.messageActionProducer != nil && a.workers != nil {
|
||||||
|
a.workers.Go(func(ctx context.Context) {
|
||||||
|
a.runMessageActionOutboxWorker(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()
|
||||||
@ -201,9 +226,13 @@ func (a *App) closeHealthHTTP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) startMQ() error {
|
func (a *App) startMQ() error {
|
||||||
|
if err := servicemq.StartProducers([]*rocketmqx.Producer{a.messageActionProducer}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return servicemq.StartConsumers(a.mqConsumers)
|
return servicemq.StartConsumers(a.mqConsumers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) shutdownMQ() {
|
func (a *App) shutdownMQ() {
|
||||||
servicemq.ShutdownConsumers(a.mqConsumers)
|
servicemq.ShutdownConsumers(a.mqConsumers)
|
||||||
|
servicemq.ShutdownProducers([]*rocketmqx.Producer{a.messageActionProducer})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ 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.RegisterMessageInboxServiceServer(server, grpcserver.NewMessageServer(services.message))
|
activityv1.RegisterMessageInboxServiceServer(server, grpcserver.NewMessageServer(services.message))
|
||||||
|
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))
|
||||||
|
|||||||
@ -80,6 +80,14 @@ func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx.
|
|||||||
}
|
}
|
||||||
mqConsumers = append(mqConsumers, consumer)
|
mqConsumers = append(mqConsumers, consumer)
|
||||||
}
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.Enabled && cfg.RocketMQ.UserOutbox.Enabled {
|
||||||
|
consumer, err := newMessageActionUserOutboxConsumer(cfg, services)
|
||||||
|
if err != nil {
|
||||||
|
shutdownConsumers(mqConsumers)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mqConsumers = append(mqConsumers, consumer)
|
||||||
|
}
|
||||||
if cfg.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
|
if cfg.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
|
||||||
consumer, err := newRedPacketWalletConsumer(cfg, services)
|
consumer, err := newRedPacketWalletConsumer(cfg, services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -317,10 +325,31 @@ func userOutboxInviteActivityConsumerConfig(cfg config.RocketMQConfig) rocketmqx
|
|||||||
return rocketMQConsumerConfig(cfg, cfg.UserOutbox.InviteActivityConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
|
return rocketMQConsumerConfig(cfg, cfg.UserOutbox.InviteActivityConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userOutboxMessageActionConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
|
||||||
|
return rocketMQConsumerConfig(cfg, cfg.UserOutbox.MessageActionConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
|
||||||
|
}
|
||||||
|
|
||||||
func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig {
|
func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig {
|
||||||
return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes)
|
return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func messageActionProducerConfig(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.MessageActionOutbox.ProducerGroup,
|
||||||
|
SendTimeout: cfg.MessageActionOutbox.SendTimeout,
|
||||||
|
Retry: cfg.MessageActionOutbox.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 != "" {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import (
|
|||||||
type serviceBundle struct {
|
type serviceBundle struct {
|
||||||
activity *activityservice.Service
|
activity *activityservice.Service
|
||||||
message *messageservice.Service
|
message *messageservice.Service
|
||||||
|
actionConfirm *messageservice.ActionConfirmService
|
||||||
task *taskservice.Service
|
task *taskservice.Service
|
||||||
registrationReward *registrationrewardservice.Service
|
registrationReward *registrationrewardservice.Service
|
||||||
sevenDayCheckIn *sevendaycheckinservice.Service
|
sevenDayCheckIn *sevendaycheckinservice.Service
|
||||||
@ -55,6 +56,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
|
|||||||
|
|
||||||
activitySvc := activityservice.New(activityservice.Config{NodeID: cfg.NodeID}, repository)
|
activitySvc := activityservice.New(activityservice.Config{NodeID: cfg.NodeID}, repository)
|
||||||
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)))
|
||||||
|
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)
|
||||||
sevenDayCheckInSvc := sevendaycheckinservice.New(repository, walletClient)
|
sevenDayCheckInSvc := sevendaycheckinservice.New(repository, walletClient)
|
||||||
@ -106,6 +108,7 @@ func buildServiceBundle(cfg config.Config, repository *mysqlstorage.Repository,
|
|||||||
return &serviceBundle{
|
return &serviceBundle{
|
||||||
activity: activitySvc,
|
activity: activitySvc,
|
||||||
message: messageSvc,
|
message: messageSvc,
|
||||||
|
actionConfirm: actionConfirmSvc,
|
||||||
task: taskSvc,
|
task: taskSvc,
|
||||||
registrationReward: registrationRewardSvc,
|
registrationReward: registrationRewardSvc,
|
||||||
sevenDayCheckIn: sevenDayCheckInSvc,
|
sevenDayCheckIn: sevenDayCheckInSvc,
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
userv1 "hyapp.local/api/proto/user/v1"
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
messageservice "hyapp/services/activity-service/internal/service/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GRPCRoleInvitationClient calls user-service because role invitation state remains owned by user-service.
|
||||||
|
type GRPCRoleInvitationClient struct {
|
||||||
|
client userv1.UserHostServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGRPCRoleInvitationClient(conn *grpc.ClientConn) *GRPCRoleInvitationClient {
|
||||||
|
return &GRPCRoleInvitationClient{client: userv1.NewUserHostServiceClient(conn)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCRoleInvitationClient) ProcessRoleInvitation(ctx context.Context, input messageservice.ProcessRoleInvitationInput) (messageservice.RoleInvitationState, error) {
|
||||||
|
resp, err := c.client.ProcessRoleInvitation(ctx, &userv1.ProcessRoleInvitationRequest{
|
||||||
|
Meta: &userv1.RequestMeta{
|
||||||
|
RequestId: "message-action-confirm-process-role-invitation",
|
||||||
|
Caller: "activity-service",
|
||||||
|
AppCode: appcode.FromContext(ctx),
|
||||||
|
},
|
||||||
|
CommandId: input.CommandID,
|
||||||
|
ActorUserId: input.ActorUserID,
|
||||||
|
InvitationId: input.InvitationID,
|
||||||
|
Action: input.Action,
|
||||||
|
Reason: input.Reason,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return messageservice.RoleInvitationState{}, err
|
||||||
|
}
|
||||||
|
return roleInvitationStateFromProto(resp.GetInvitation()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GRPCRoleInvitationClient) GetRoleInvitation(ctx context.Context, actorUserID int64, invitationID int64) (messageservice.RoleInvitationState, error) {
|
||||||
|
resp, err := c.client.GetRoleInvitation(ctx, &userv1.GetRoleInvitationRequest{
|
||||||
|
Meta: &userv1.RequestMeta{
|
||||||
|
RequestId: "message-action-confirm-get-role-invitation",
|
||||||
|
Caller: "activity-service",
|
||||||
|
AppCode: appcode.FromContext(ctx),
|
||||||
|
},
|
||||||
|
ActorUserId: actorUserID,
|
||||||
|
InvitationId: invitationID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return messageservice.RoleInvitationState{}, err
|
||||||
|
}
|
||||||
|
return roleInvitationStateFromProto(resp.GetInvitation()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleInvitationStateFromProto(invitation *userv1.RoleInvitation) messageservice.RoleInvitationState {
|
||||||
|
if invitation == nil {
|
||||||
|
return messageservice.RoleInvitationState{}
|
||||||
|
}
|
||||||
|
return messageservice.RoleInvitationState{
|
||||||
|
InvitationID: invitation.GetInvitationId(),
|
||||||
|
InvitationType: invitation.GetInvitationType(),
|
||||||
|
Status: invitation.GetStatus(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -49,7 +49,9 @@ type Config struct {
|
|||||||
MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"`
|
MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"`
|
||||||
Consumer ConsumerConfig `yaml:"consumer"`
|
Consumer ConsumerConfig `yaml:"consumer"`
|
||||||
MessageInbox MessageInboxConfig `yaml:"message_inbox"`
|
MessageInbox MessageInboxConfig `yaml:"message_inbox"`
|
||||||
Log logx.Config `yaml:"log"`
|
// MessageActionConfirmWorker 控制通用确认消息的 user_outbox 消费和 IM outbox 发布。
|
||||||
|
MessageActionConfirmWorker MessageActionConfirmWorkerConfig `yaml:"message_action_confirm_worker"`
|
||||||
|
Log logx.Config `yaml:"log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsumerConfig 保存 room outbox 消费底座配置。
|
// ConsumerConfig 保存 room outbox 消费底座配置。
|
||||||
@ -63,6 +65,16 @@ type MessageInboxConfig struct {
|
|||||||
UnreadCacheTTL string `yaml:"unread_cache_ttl"`
|
UnreadCacheTTL string `yaml:"unread_cache_ttl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageActionConfirmWorkerConfig 保存确认消息消费和 MQ 发布策略。
|
||||||
|
type MessageActionConfirmWorkerConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
OutboxPollInterval time.Duration `yaml:"outbox_poll_interval"`
|
||||||
|
OutboxBatchSize int `yaml:"outbox_batch_size"`
|
||||||
|
OutboxLockTTL time.Duration `yaml:"outbox_lock_ttl"`
|
||||||
|
OutboxMaxRetry int `yaml:"outbox_max_retry"`
|
||||||
|
PublishTimeout time.Duration `yaml:"publish_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
// FirstRechargeRewardWorkerConfig 保存钱包充值事实消费策略。
|
// FirstRechargeRewardWorkerConfig 保存钱包充值事实消费策略。
|
||||||
type FirstRechargeRewardWorkerConfig struct {
|
type FirstRechargeRewardWorkerConfig struct {
|
||||||
// Enabled 控制是否启动 wallet_outbox MQ 消费;关闭后仍可通过 gRPC 消费入口补偿。
|
// Enabled 控制是否启动 wallet_outbox MQ 消费;关闭后仍可通过 gRPC 消费入口补偿。
|
||||||
@ -149,17 +161,18 @@ 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"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomOutboxMQConfig 控制 activity 对 room_outbox topic 的消费位点。
|
// RoomOutboxMQConfig 控制 activity 对 room_outbox topic 的消费位点。
|
||||||
@ -190,9 +203,19 @@ type UserOutboxMQConfig struct {
|
|||||||
ConsumerGroup string `yaml:"consumer_group"`
|
ConsumerGroup string `yaml:"consumer_group"`
|
||||||
TaskConsumerGroup string `yaml:"task_consumer_group"`
|
TaskConsumerGroup string `yaml:"task_consumer_group"`
|
||||||
InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"`
|
InviteActivityConsumerGroup string `yaml:"invite_activity_consumer_group"`
|
||||||
|
MessageActionConsumerGroup string `yaml:"message_action_consumer_group"`
|
||||||
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
|
ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageActionOutboxMQConfig 控制确认消息 outbox 投递到 notice-service 的 MQ producer。
|
||||||
|
type MessageActionOutboxMQConfig 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{
|
||||||
@ -220,6 +243,14 @@ func Default() Config {
|
|||||||
TaskEventWorker: TaskEventWorkerConfig{
|
TaskEventWorker: TaskEventWorkerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
|
MessageActionConfirmWorker: MessageActionConfirmWorkerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
OutboxPollInterval: time.Second,
|
||||||
|
OutboxBatchSize: 100,
|
||||||
|
OutboxLockTTL: 30 * time.Second,
|
||||||
|
OutboxMaxRetry: 8,
|
||||||
|
PublishTimeout: 5 * time.Second,
|
||||||
|
},
|
||||||
LuckyGiftWorker: LuckyGiftWorkerConfig{
|
LuckyGiftWorker: LuckyGiftWorkerConfig{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
WorkerPollInterval: time.Second,
|
WorkerPollInterval: time.Second,
|
||||||
@ -291,8 +322,16 @@ func defaultRocketMQConfig() RocketMQConfig {
|
|||||||
ConsumerGroup: "hyapp-activity-user-region-broadcast",
|
ConsumerGroup: "hyapp-activity-user-region-broadcast",
|
||||||
TaskConsumerGroup: "hyapp-activity-task-user-outbox",
|
TaskConsumerGroup: "hyapp-activity-task-user-outbox",
|
||||||
InviteActivityConsumerGroup: "hyapp-activity-invite-user-outbox",
|
InviteActivityConsumerGroup: "hyapp-activity-invite-user-outbox",
|
||||||
|
MessageActionConsumerGroup: "hyapp-activity-message-action-user-outbox",
|
||||||
ConsumerMaxReconsumeTimes: 16,
|
ConsumerMaxReconsumeTimes: 16,
|
||||||
},
|
},
|
||||||
|
MessageActionOutbox: MessageActionOutboxMQConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Topic: "hyapp_message_action_outbox",
|
||||||
|
ProducerGroup: "hyapp-activity-message-action-outbox",
|
||||||
|
SendTimeout: 5 * time.Second,
|
||||||
|
Retry: 2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +425,21 @@ func Load(path string) (Config, error) {
|
|||||||
if cfg.LuckyGiftWorker.PublishTimeout <= 0 {
|
if cfg.LuckyGiftWorker.PublishTimeout <= 0 {
|
||||||
cfg.LuckyGiftWorker.PublishTimeout = 5 * time.Second
|
cfg.LuckyGiftWorker.PublishTimeout = 5 * time.Second
|
||||||
}
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.OutboxPollInterval <= 0 {
|
||||||
|
cfg.MessageActionConfirmWorker.OutboxPollInterval = time.Second
|
||||||
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.OutboxBatchSize <= 0 {
|
||||||
|
cfg.MessageActionConfirmWorker.OutboxBatchSize = 100
|
||||||
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.OutboxLockTTL <= 0 {
|
||||||
|
cfg.MessageActionConfirmWorker.OutboxLockTTL = 30 * time.Second
|
||||||
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.OutboxMaxRetry <= 0 {
|
||||||
|
cfg.MessageActionConfirmWorker.OutboxMaxRetry = 8
|
||||||
|
}
|
||||||
|
if cfg.MessageActionConfirmWorker.PublishTimeout <= 0 {
|
||||||
|
cfg.MessageActionConfirmWorker.PublishTimeout = 5 * time.Second
|
||||||
|
}
|
||||||
rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
|
rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, err
|
return Config{}, err
|
||||||
@ -397,6 +451,9 @@ func Load(path string) (Config, error) {
|
|||||||
if cfg.InviteActivityRewardWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled {
|
if cfg.InviteActivityRewardWorker.Enabled && !cfg.RocketMQ.UserOutbox.Enabled {
|
||||||
return Config{}, errors.New("invite activity reward worker requires rocketmq.user_outbox.enabled")
|
return Config{}, errors.New("invite activity reward worker requires rocketmq.user_outbox.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")
|
||||||
|
}
|
||||||
if cfg.LuckyGiftWorker.Enabled && !cfg.TencentIM.Enabled {
|
if cfg.LuckyGiftWorker.Enabled && !cfg.TencentIM.Enabled {
|
||||||
return Config{}, errors.New("lucky gift worker requires tencent_im.enabled")
|
return Config{}, errors.New("lucky gift worker requires tencent_im.enabled")
|
||||||
}
|
}
|
||||||
@ -455,10 +512,25 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) {
|
|||||||
if cfg.UserOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.UserOutbox.InviteActivityConsumerGroup); cfg.UserOutbox.InviteActivityConsumerGroup == "" {
|
if cfg.UserOutbox.InviteActivityConsumerGroup = strings.TrimSpace(cfg.UserOutbox.InviteActivityConsumerGroup); cfg.UserOutbox.InviteActivityConsumerGroup == "" {
|
||||||
cfg.UserOutbox.InviteActivityConsumerGroup = defaults.UserOutbox.InviteActivityConsumerGroup
|
cfg.UserOutbox.InviteActivityConsumerGroup = defaults.UserOutbox.InviteActivityConsumerGroup
|
||||||
}
|
}
|
||||||
|
if cfg.UserOutbox.MessageActionConsumerGroup = strings.TrimSpace(cfg.UserOutbox.MessageActionConsumerGroup); cfg.UserOutbox.MessageActionConsumerGroup == "" {
|
||||||
|
cfg.UserOutbox.MessageActionConsumerGroup = defaults.UserOutbox.MessageActionConsumerGroup
|
||||||
|
}
|
||||||
if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 {
|
if cfg.UserOutbox.ConsumerMaxReconsumeTimes <= 0 {
|
||||||
cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes
|
cfg.UserOutbox.ConsumerMaxReconsumeTimes = defaults.UserOutbox.ConsumerMaxReconsumeTimes
|
||||||
}
|
}
|
||||||
if cfg.RoomOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled {
|
if cfg.MessageActionOutbox.Topic = strings.TrimSpace(cfg.MessageActionOutbox.Topic); cfg.MessageActionOutbox.Topic == "" {
|
||||||
|
cfg.MessageActionOutbox.Topic = defaults.MessageActionOutbox.Topic
|
||||||
|
}
|
||||||
|
if cfg.MessageActionOutbox.ProducerGroup = strings.TrimSpace(cfg.MessageActionOutbox.ProducerGroup); cfg.MessageActionOutbox.ProducerGroup == "" {
|
||||||
|
cfg.MessageActionOutbox.ProducerGroup = defaults.MessageActionOutbox.ProducerGroup
|
||||||
|
}
|
||||||
|
if cfg.MessageActionOutbox.SendTimeout <= 0 {
|
||||||
|
cfg.MessageActionOutbox.SendTimeout = defaults.MessageActionOutbox.SendTimeout
|
||||||
|
}
|
||||||
|
if cfg.MessageActionOutbox.Retry < 0 {
|
||||||
|
cfg.MessageActionOutbox.Retry = defaults.MessageActionOutbox.Retry
|
||||||
|
}
|
||||||
|
if cfg.RoomOutbox.Enabled || cfg.WalletOutbox.Enabled || cfg.UserOutbox.Enabled || cfg.MessageActionOutbox.Enabled {
|
||||||
cfg.Enabled = true
|
cfg.Enabled = true
|
||||||
}
|
}
|
||||||
if cfg.Enabled {
|
if cfg.Enabled {
|
||||||
|
|||||||
@ -77,9 +77,10 @@ type Settlement struct {
|
|||||||
|
|
||||||
// Status is the H5 read model for current week leaderboard and configured rewards.
|
// Status is the H5 read model for current week leaderboard and configured rewards.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Config Config
|
Config Config
|
||||||
Entries []Entry
|
Entries []Entry
|
||||||
PeriodStartMS int64
|
PreviousEntries []Entry
|
||||||
PeriodEndMS int64
|
PeriodStartMS int64
|
||||||
ServerTimeMS int64
|
PeriodEndMS int64
|
||||||
|
ServerTimeMS int64
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionConfirmBusinessRoleInvitation = "role_invitation"
|
||||||
|
|
||||||
|
ActionConfirmSourceUserOutbox = "user_outbox"
|
||||||
|
|
||||||
|
ActionConfirmEventCreated = "ConfirmMessageCreated"
|
||||||
|
|
||||||
|
ActionConfirmStatusPending = "pending"
|
||||||
|
ActionConfirmStatusProcessing = "processing"
|
||||||
|
ActionConfirmStatusAccepted = "accepted"
|
||||||
|
ActionConfirmStatusRejected = "rejected"
|
||||||
|
ActionConfirmStatusExpired = "expired"
|
||||||
|
ActionConfirmStatusCanceled = "canceled"
|
||||||
|
ActionConfirmStatusFailed = "failed"
|
||||||
|
|
||||||
|
ActionConfirmActionAccept = "accept"
|
||||||
|
ActionConfirmActionReject = "reject"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionConfirm 是 IM 操作按钮的后端状态机;App 只能拿 confirm_id 操作,业务参数全部来自这行事实。
|
||||||
|
type ActionConfirm struct {
|
||||||
|
AppCode string
|
||||||
|
ConfirmID string
|
||||||
|
SourceName string
|
||||||
|
SourceEventID string
|
||||||
|
BusinessType string
|
||||||
|
BusinessSubtype string
|
||||||
|
BusinessID string
|
||||||
|
SenderUserID int64
|
||||||
|
ReceiverUserID int64
|
||||||
|
MessageText string
|
||||||
|
Status string
|
||||||
|
Action string
|
||||||
|
CommandID string
|
||||||
|
ProcessedAtMS int64
|
||||||
|
LockedBy string
|
||||||
|
LockUntilMS int64
|
||||||
|
ErrorMessage string
|
||||||
|
MetadataJSON string
|
||||||
|
CreatedAtMS int64
|
||||||
|
UpdatedAtMS int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionConfirmOutbox 是确认消息创建后交给 notice-service 投递 IM 的可靠事件箱。
|
||||||
|
type ActionConfirmOutbox struct {
|
||||||
|
AppCode string
|
||||||
|
EventID string
|
||||||
|
ConfirmID string
|
||||||
|
EventType string
|
||||||
|
Status string
|
||||||
|
WorkerID string
|
||||||
|
LockUntilMS int64
|
||||||
|
RetryCount int
|
||||||
|
NextRetryAtMS int64
|
||||||
|
LastError string
|
||||||
|
PayloadJSON string
|
||||||
|
CreatedAtMS int64
|
||||||
|
UpdatedAtMS int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleInvitationConfirmEvent 是 user_outbox 中 RoleInvitationCreated 的最小投影。
|
||||||
|
type RoleInvitationConfirmEvent struct {
|
||||||
|
SourceEventID string
|
||||||
|
InvitationID int64
|
||||||
|
InvitationType string
|
||||||
|
InviterUserID int64
|
||||||
|
TargetUserID int64
|
||||||
|
AgencyName string
|
||||||
|
Inviter RoleInvitationUserSnapshot
|
||||||
|
Target RoleInvitationUserSnapshot
|
||||||
|
CreatedAtMS int64
|
||||||
|
RawPayloadJSON string
|
||||||
|
ParentBDUserID int64
|
||||||
|
ParentLeaderUserID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleInvitationUserSnapshot 是生成 IM 展示文案时使用的用户资料快照,不作为权限依据。
|
||||||
|
type RoleInvitationUserSnapshot struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
DisplayUserID string `json:"display_user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
}
|
||||||
@ -120,11 +120,19 @@ func (s *Service) GetStatus(ctx context.Context, limit int32, nowMS int64) (doma
|
|||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
limit = 3
|
limit = 3
|
||||||
}
|
}
|
||||||
|
// H5 同时需要当前周期榜单和上一完整 UTC 周期的前三名;两个窗口都从 user-service 的周期新增 gift_value 聚合读取,避免 gateway/H5 误用累计亲密榜。
|
||||||
entries, err := s.rankSource.ListCPWeeklyRankEntries(ctx, config.RelationType, result.PeriodStartMS, result.PeriodEndMS, limit)
|
entries, err := s.rankSource.ListCPWeeklyRankEntries(ctx, config.RelationType, result.PeriodStartMS, result.PeriodEndMS, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Status{}, err
|
return domain.Status{}, err
|
||||||
}
|
}
|
||||||
result.Entries = entries
|
result.Entries = entries
|
||||||
|
previousStart, previousEnd := PreviousWeekWindow(now)
|
||||||
|
previousLimit := int32(3)
|
||||||
|
previousEntries, err := s.rankSource.ListCPWeeklyRankEntries(ctx, config.RelationType, previousStart.UnixMilli(), previousEnd.UnixMilli(), previousLimit)
|
||||||
|
if err != nil {
|
||||||
|
return domain.Status{}, err
|
||||||
|
}
|
||||||
|
result.PreviousEntries = previousEntries
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +144,7 @@ func (s *Service) ListSettlements(ctx context.Context, periodStartMS int64, peri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProcessSettlementBatch freezes the previous complete UTC week and grants each Top relationship reward to both users.
|
// ProcessSettlementBatch freezes the previous complete UTC week and grants each Top relationship reward to both users.
|
||||||
|
// The rank source already returns weekly delta score, so settlement only persists that frozen score and never re-reads cumulative intimacy.
|
||||||
func (s *Service) ProcessSettlementBatch(ctx context.Context, runID string, workerID string, batchSize int32) (claimed int32, processed int32, success int32, failure int32, hasMore bool, err error) {
|
func (s *Service) ProcessSettlementBatch(ctx context.Context, runID string, workerID string, batchSize int32) (claimed int32, processed int32, success int32, failure int32, hasMore bool, err error) {
|
||||||
if err := s.requireRepository(); err != nil {
|
if err := s.requireRepository(); err != nil {
|
||||||
return 0, 0, 0, 0, false, err
|
return 0, 0, 0, 0, false, err
|
||||||
|
|||||||
@ -0,0 +1,142 @@
|
|||||||
|
package cpweeklyrank_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
domain "hyapp/services/activity-service/internal/domain/cpweeklyrank"
|
||||||
|
service "hyapp/services/activity-service/internal/service/cpweeklyrank"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStatusReturnsPreviousEntriesFromPreviousWeek(t *testing.T) {
|
||||||
|
now := time.Date(2026, 6, 24, 12, 0, 0, 0, time.UTC)
|
||||||
|
currentStart, currentEnd := service.WeekWindow(now)
|
||||||
|
previousStart, previousEnd := service.PreviousWeekWindow(now)
|
||||||
|
rankSource := &fakeCPWeeklyRankSource{
|
||||||
|
windows: map[[2]int64][]domain.Entry{
|
||||||
|
{currentStart.UnixMilli(), currentEnd.UnixMilli()}: cpWeeklyRankEntries(2, 1000),
|
||||||
|
{previousStart.UnixMilli(), previousEnd.UnixMilli()}: cpWeeklyRankEntries(5, 5000),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
svc := service.New(&fakeCPWeeklyRankRepository{config: domain.Config{
|
||||||
|
AppCode: "lalu",
|
||||||
|
ActivityCode: domain.ActivityCode,
|
||||||
|
Enabled: true,
|
||||||
|
RelationType: "cp",
|
||||||
|
TopCount: 10,
|
||||||
|
}}, rankSource, nil)
|
||||||
|
svc.SetClock(func() time.Time { return now })
|
||||||
|
|
||||||
|
status, err := svc.GetStatus(appcode.WithContext(context.Background(), "lalu"), 1, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetStatus failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(status.Entries) != 1 {
|
||||||
|
t.Fatalf("current entries mismatch: %+v", status.Entries)
|
||||||
|
}
|
||||||
|
if len(status.PreviousEntries) != 3 {
|
||||||
|
t.Fatalf("previous entries should be limited to 3, got %+v", status.PreviousEntries)
|
||||||
|
}
|
||||||
|
if status.PreviousEntries[0].UserA.Username != "left-1" || status.PreviousEntries[0].UserA.Avatar != "https://avatar/left-1.png" ||
|
||||||
|
status.PreviousEntries[0].UserB.Username != "right-1" || status.PreviousEntries[0].UserB.Avatar != "https://avatar/right-1.png" ||
|
||||||
|
status.PreviousEntries[0].Score != 5001 {
|
||||||
|
t.Fatalf("previous entry should keep user snapshots and intimacy score: %+v", status.PreviousEntries[0])
|
||||||
|
}
|
||||||
|
if len(rankSource.calls) != 2 {
|
||||||
|
t.Fatalf("rank source call count mismatch: %+v", rankSource.calls)
|
||||||
|
}
|
||||||
|
if rankSource.calls[0].startMS != currentStart.UnixMilli() || rankSource.calls[0].endMS != currentEnd.UnixMilli() || rankSource.calls[0].limit != 1 {
|
||||||
|
t.Fatalf("current window call mismatch: %+v", rankSource.calls[0])
|
||||||
|
}
|
||||||
|
if rankSource.calls[1].startMS != previousStart.UnixMilli() || rankSource.calls[1].endMS != previousEnd.UnixMilli() || rankSource.calls[1].limit != 3 {
|
||||||
|
t.Fatalf("previous window call mismatch: %+v", rankSource.calls[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeCPWeeklyRankRepository struct {
|
||||||
|
config domain.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) GetCPWeeklyRankConfig(context.Context) (domain.Config, bool, error) {
|
||||||
|
return r.config, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) UpdateCPWeeklyRankConfig(context.Context, domain.Config, int64) (domain.Config, error) {
|
||||||
|
return domain.Config{}, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) ListCPWeeklyRankSettlements(context.Context, int64, int64) ([]domain.Settlement, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) PrepareCPWeeklyRankSettlements(context.Context, domain.Config, []domain.Entry, int64, int64, int64) ([]domain.Settlement, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) ListPendingCPWeeklyRankSettlements(context.Context, int64, int64, int) ([]domain.Settlement, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) MarkCPWeeklyRankSettlementGranted(context.Context, string, string, int64) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeCPWeeklyRankRepository) MarkCPWeeklyRankSettlementFailed(context.Context, string, string, int64) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeCPWeeklyRankSource struct {
|
||||||
|
calls []fakeCPWeeklyRankSourceCall
|
||||||
|
windows map[[2]int64][]domain.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeCPWeeklyRankSourceCall struct {
|
||||||
|
relationType string
|
||||||
|
startMS int64
|
||||||
|
endMS int64
|
||||||
|
limit int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeCPWeeklyRankSource) ListCPWeeklyRankEntries(_ context.Context, relationType string, startMS int64, endMS int64, limit int32) ([]domain.Entry, error) {
|
||||||
|
s.calls = append(s.calls, fakeCPWeeklyRankSourceCall{
|
||||||
|
relationType: relationType,
|
||||||
|
startMS: startMS,
|
||||||
|
endMS: endMS,
|
||||||
|
limit: limit,
|
||||||
|
})
|
||||||
|
entries := append([]domain.Entry(nil), s.windows[[2]int64{startMS, endMS}]...)
|
||||||
|
if limit > 0 && len(entries) > int(limit) {
|
||||||
|
entries = entries[:limit]
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpWeeklyRankEntries(count int, baseScore int64) []domain.Entry {
|
||||||
|
entries := make([]domain.Entry, 0, count)
|
||||||
|
for index := 1; index <= count; index++ {
|
||||||
|
suffix := strconv.Itoa(index)
|
||||||
|
entries = append(entries, domain.Entry{
|
||||||
|
Rank: int64(index),
|
||||||
|
RelationshipID: "cp-rel-" + suffix,
|
||||||
|
RelationType: "cp",
|
||||||
|
Score: baseScore + int64(index),
|
||||||
|
UserA: domain.User{
|
||||||
|
UserID: int64(1000 + index),
|
||||||
|
DisplayUserID: "L" + suffix,
|
||||||
|
Username: "left-" + suffix,
|
||||||
|
Avatar: "https://avatar/left-" + suffix + ".png",
|
||||||
|
},
|
||||||
|
UserB: domain.User{
|
||||||
|
UserID: int64(2000 + index),
|
||||||
|
DisplayUserID: "R" + suffix,
|
||||||
|
Username: "right-" + suffix,
|
||||||
|
Avatar: "https://avatar/right-" + suffix + ".png",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
@ -0,0 +1,443 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp/pkg/idgen"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
messagedomain "hyapp/services/activity-service/internal/domain/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
actionConfirmDefaultLockTTL = 30 * time.Second
|
||||||
|
actionConfirmDefaultLimit = 50
|
||||||
|
actionConfirmMaxLimit = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionConfirmRepository is the durable boundary for confirm rows and their IM outbox.
|
||||||
|
type ActionConfirmRepository interface {
|
||||||
|
SaveActionConfirmWithOutbox(ctx context.Context, confirm messagedomain.ActionConfirm, outbox messagedomain.ActionConfirmOutbox) (bool, messagedomain.ActionConfirm, error)
|
||||||
|
ClaimActionConfirm(ctx context.Context, confirmID string, actorUserID int64, action string, commandID string, workerID string, nowMS int64, lockUntilMS int64) (messagedomain.ActionConfirm, bool, error)
|
||||||
|
CompleteActionConfirm(ctx context.Context, confirmID string, commandID string, status string, action string, processedAtMS int64) (messagedomain.ActionConfirm, error)
|
||||||
|
ReleaseActionConfirm(ctx context.Context, confirmID string, commandID string, errorMessage string, nowMS int64) error
|
||||||
|
BatchGetActionConfirms(ctx context.Context, userID int64, confirmIDs []string) ([]messagedomain.ActionConfirm, error)
|
||||||
|
ListConversationActionConfirms(ctx context.Context, userID int64, peerUserID int64, limit int, cursorCreatedAtMS int64, cursorConfirmID string) ([]messagedomain.ActionConfirm, error)
|
||||||
|
ClaimPendingActionConfirmOutbox(ctx context.Context, workerID string, nowMS int64, lockUntilMS int64, batchSize int) ([]messagedomain.ActionConfirmOutbox, error)
|
||||||
|
MarkActionConfirmOutboxDelivered(ctx context.Context, eventID string, nowMS int64) error
|
||||||
|
MarkActionConfirmOutboxRetryable(ctx context.Context, eventID string, lastErr string, nextRetryAtMS int64, nowMS int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleInvitationClient is the owner-service boundary used when a confirm action maps to a role invitation.
|
||||||
|
type RoleInvitationClient interface {
|
||||||
|
ProcessRoleInvitation(ctx context.Context, input ProcessRoleInvitationInput) (RoleInvitationState, error)
|
||||||
|
GetRoleInvitation(ctx context.Context, actorUserID int64, invitationID int64) (RoleInvitationState, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessRoleInvitationInput is the normalized owner-service command assembled from a confirm row.
|
||||||
|
type ProcessRoleInvitationInput struct {
|
||||||
|
CommandID string
|
||||||
|
ActorUserID int64
|
||||||
|
InvitationID int64
|
||||||
|
Action string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleInvitationState is the small role-invitation projection needed to reconcile confirm status.
|
||||||
|
type RoleInvitationState struct {
|
||||||
|
InvitationID int64
|
||||||
|
InvitationType string
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionConfirmService owns IM confirm status and delegates business mutations back to owner services.
|
||||||
|
type ActionConfirmService struct {
|
||||||
|
cfg Config
|
||||||
|
repository ActionConfirmRepository
|
||||||
|
roleInvitationClient RoleInvitationClient
|
||||||
|
now func() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewActionConfirm creates the generic confirm-message service.
|
||||||
|
func NewActionConfirm(cfg Config, repository ActionConfirmRepository, roleInvitationClient RoleInvitationClient) *ActionConfirmService {
|
||||||
|
return &ActionConfirmService{cfg: cfg, repository: repository, roleInvitationClient: roleInvitationClient, now: time.Now}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClock injects deterministic time for tests.
|
||||||
|
func (s *ActionConfirmService) SetClock(now func() time.Time) {
|
||||||
|
if now != nil {
|
||||||
|
s.now = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeRoleInvitationCreated materializes one user-service invitation fact into an actionable IM confirm.
|
||||||
|
func (s *ActionConfirmService) ConsumeRoleInvitationCreated(ctx context.Context, event messagedomain.RoleInvitationConfirmEvent) (messagedomain.ActionConfirm, bool, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
if event.SourceEventID == "" || event.InvitationID <= 0 || event.InviterUserID <= 0 || event.TargetUserID <= 0 {
|
||||||
|
return messagedomain.ActionConfirm{}, false, xerr.New(xerr.InvalidArgument, "role invitation confirm event is incomplete")
|
||||||
|
}
|
||||||
|
subtype := strings.TrimSpace(event.InvitationType)
|
||||||
|
if subtype == "" {
|
||||||
|
return messagedomain.ActionConfirm{}, false, xerr.New(xerr.InvalidArgument, "invitation_type is required")
|
||||||
|
}
|
||||||
|
nowMS := firstPositiveInt64(event.CreatedAtMS, s.now().UnixMilli())
|
||||||
|
messageText := roleInvitationMessageText(event)
|
||||||
|
metadataJSON := strings.TrimSpace(event.RawPayloadJSON)
|
||||||
|
if metadataJSON == "" || !json.Valid([]byte(metadataJSON)) {
|
||||||
|
metadataJSON = "{}"
|
||||||
|
}
|
||||||
|
confirmID := idgen.New("cfm")
|
||||||
|
payloadJSON, err := confirmIMPayloadJSON(confirmID, event, messageText, nowMS)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
confirm := messagedomain.ActionConfirm{
|
||||||
|
ConfirmID: confirmID,
|
||||||
|
SourceName: messagedomain.ActionConfirmSourceUserOutbox,
|
||||||
|
SourceEventID: event.SourceEventID,
|
||||||
|
BusinessType: messagedomain.ActionConfirmBusinessRoleInvitation,
|
||||||
|
BusinessSubtype: subtype,
|
||||||
|
BusinessID: strconv.FormatInt(event.InvitationID, 10),
|
||||||
|
SenderUserID: event.InviterUserID,
|
||||||
|
ReceiverUserID: event.TargetUserID,
|
||||||
|
MessageText: messageText,
|
||||||
|
Status: messagedomain.ActionConfirmStatusPending,
|
||||||
|
MetadataJSON: metadataJSON,
|
||||||
|
CreatedAtMS: nowMS,
|
||||||
|
UpdatedAtMS: nowMS,
|
||||||
|
}
|
||||||
|
outbox := messagedomain.ActionConfirmOutbox{
|
||||||
|
EventID: idgen.New("maout"),
|
||||||
|
ConfirmID: confirm.ConfirmID,
|
||||||
|
EventType: messagedomain.ActionConfirmEventCreated,
|
||||||
|
Status: "pending",
|
||||||
|
PayloadJSON: payloadJSON,
|
||||||
|
CreatedAtMS: nowMS,
|
||||||
|
UpdatedAtMS: nowMS,
|
||||||
|
}
|
||||||
|
created, saved, err := s.repository.SaveActionConfirmWithOutbox(ctx, confirm, outbox)
|
||||||
|
return saved, created, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptActionConfirm executes the accept branch for the current receiver.
|
||||||
|
func (s *ActionConfirmService) AcceptActionConfirm(ctx context.Context, actorUserID int64, confirmID string, commandID string) (messagedomain.ActionConfirm, error) {
|
||||||
|
return s.processAction(ctx, actorUserID, confirmID, commandID, messagedomain.ActionConfirmActionAccept, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RejectActionConfirm executes the reject branch for the current receiver.
|
||||||
|
func (s *ActionConfirmService) RejectActionConfirm(ctx context.Context, actorUserID int64, confirmID string, commandID string, reason string) (messagedomain.ActionConfirm, error) {
|
||||||
|
return s.processAction(ctx, actorUserID, confirmID, commandID, messagedomain.ActionConfirmActionReject, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) processAction(ctx context.Context, actorUserID int64, confirmID string, commandID string, action string, reason string) (messagedomain.ActionConfirm, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
if s.roleInvitationClient == nil {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.Unavailable, "role invitation client is not configured")
|
||||||
|
}
|
||||||
|
confirmID = strings.TrimSpace(confirmID)
|
||||||
|
commandID = strings.TrimSpace(commandID)
|
||||||
|
if actorUserID <= 0 || confirmID == "" || commandID == "" {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.InvalidArgument, "actor_user_id, confirm_id and command_id are required")
|
||||||
|
}
|
||||||
|
nowMS := s.now().UnixMilli()
|
||||||
|
claimed, ok, err := s.repository.ClaimActionConfirm(ctx, confirmID, actorUserID, action, commandID, s.workerID(), nowMS, nowMS+actionConfirmDefaultLockTTL.Milliseconds())
|
||||||
|
if err != nil || !ok {
|
||||||
|
return claimed, err
|
||||||
|
}
|
||||||
|
if isTerminalConfirmStatus(claimed.Status) {
|
||||||
|
return claimed, nil
|
||||||
|
}
|
||||||
|
invitationID, err := strconv.ParseInt(claimed.BusinessID, 10, 64)
|
||||||
|
if err != nil || invitationID <= 0 {
|
||||||
|
_ = s.repository.ReleaseActionConfirm(ctx, confirmID, commandID, "invalid business_id", s.now().UnixMilli())
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.InvalidArgument, "confirm business_id is invalid")
|
||||||
|
}
|
||||||
|
if claimed.BusinessType != messagedomain.ActionConfirmBusinessRoleInvitation {
|
||||||
|
_ = s.repository.ReleaseActionConfirm(ctx, confirmID, commandID, "unsupported business_type", s.now().UnixMilli())
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.InvalidArgument, "confirm business_type is unsupported")
|
||||||
|
}
|
||||||
|
state, err := s.roleInvitationClient.ProcessRoleInvitation(ctx, ProcessRoleInvitationInput{
|
||||||
|
CommandID: commandID,
|
||||||
|
ActorUserID: actorUserID,
|
||||||
|
InvitationID: invitationID,
|
||||||
|
Action: action,
|
||||||
|
Reason: strings.TrimSpace(reason),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
reconciled, reconcileErr := s.reconcileRoleInvitation(ctx, claimed, actorUserID, commandID, action)
|
||||||
|
if reconcileErr == nil && isTerminalConfirmStatus(reconciled.Status) {
|
||||||
|
return reconciled, nil
|
||||||
|
}
|
||||||
|
_ = s.repository.ReleaseActionConfirm(ctx, confirmID, commandID, err.Error(), s.now().UnixMilli())
|
||||||
|
return messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
return s.repository.CompleteActionConfirm(ctx, confirmID, commandID, confirmStatusFromRoleInvitation(state.Status), actionFromRoleInvitationStatus(state.Status, action), s.now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) reconcileRoleInvitation(ctx context.Context, confirm messagedomain.ActionConfirm, actorUserID int64, commandID string, action string) (messagedomain.ActionConfirm, error) {
|
||||||
|
invitationID, err := strconv.ParseInt(confirm.BusinessID, 10, 64)
|
||||||
|
if err != nil || invitationID <= 0 {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.InvalidArgument, "confirm business_id is invalid")
|
||||||
|
}
|
||||||
|
state, err := s.roleInvitationClient.GetRoleInvitation(ctx, actorUserID, invitationID)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
status := confirmStatusFromRoleInvitation(state.Status)
|
||||||
|
if !isTerminalConfirmStatus(status) {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.Conflict, "role invitation is not terminal")
|
||||||
|
}
|
||||||
|
return s.repository.CompleteActionConfirm(ctx, confirm.ConfirmID, commandID, status, actionFromRoleInvitationStatus(state.Status, action), s.now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchGetActionConfirmStatus returns only confirms visible to the current user.
|
||||||
|
func (s *ActionConfirmService) BatchGetActionConfirmStatus(ctx context.Context, userID int64, confirmIDs []string) ([]messagedomain.ActionConfirm, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userID <= 0 {
|
||||||
|
return nil, xerr.New(xerr.InvalidArgument, "user_id is required")
|
||||||
|
}
|
||||||
|
normalized := make([]string, 0, len(confirmIDs))
|
||||||
|
seen := make(map[string]struct{}, len(confirmIDs))
|
||||||
|
for _, confirmID := range confirmIDs {
|
||||||
|
confirmID = strings.TrimSpace(confirmID)
|
||||||
|
if confirmID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[confirmID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[confirmID] = struct{}{}
|
||||||
|
normalized = append(normalized, confirmID)
|
||||||
|
}
|
||||||
|
if len(normalized) == 0 {
|
||||||
|
return []messagedomain.ActionConfirm{}, nil
|
||||||
|
}
|
||||||
|
if len(normalized) > 100 {
|
||||||
|
return nil, xerr.New(xerr.InvalidArgument, "confirm_ids is too large")
|
||||||
|
}
|
||||||
|
return s.repository.BatchGetActionConfirms(ctx, userID, normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConversationActionConfirms returns confirm states for one C2C peer conversation.
|
||||||
|
func (s *ActionConfirmService) ListConversationActionConfirms(ctx context.Context, userID int64, peerUserID int64, pageSize int32, pageToken string) ([]messagedomain.ActionConfirm, string, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if userID <= 0 || peerUserID <= 0 {
|
||||||
|
return nil, "", xerr.New(xerr.InvalidArgument, "user_id and peer_user_id are required")
|
||||||
|
}
|
||||||
|
limit := int(pageSize)
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = actionConfirmDefaultLimit
|
||||||
|
}
|
||||||
|
if limit > actionConfirmMaxLimit {
|
||||||
|
limit = actionConfirmMaxLimit
|
||||||
|
}
|
||||||
|
cursor, err := decodeActionConfirmCursor(pageToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
items, err := s.repository.ListConversationActionConfirms(ctx, userID, peerUserID, limit+1, cursor.CreatedAtMS, cursor.ConfirmID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if len(items) <= limit {
|
||||||
|
return items, "", nil
|
||||||
|
}
|
||||||
|
page := items[:limit]
|
||||||
|
return page, encodeActionConfirmCursor(page[len(page)-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimPendingOutbox locks pending action-message outbox rows for the MQ publisher.
|
||||||
|
func (s *ActionConfirmService) ClaimPendingOutbox(ctx context.Context, batchSize int, lockTTL time.Duration) ([]messagedomain.ActionConfirmOutbox, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if batchSize <= 0 {
|
||||||
|
batchSize = 100
|
||||||
|
}
|
||||||
|
if lockTTL <= 0 {
|
||||||
|
lockTTL = 30 * time.Second
|
||||||
|
}
|
||||||
|
nowMS := s.now().UnixMilli()
|
||||||
|
return s.repository.ClaimPendingActionConfirmOutbox(ctx, s.workerID(), nowMS, nowMS+lockTTL.Milliseconds(), batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) MarkOutboxDelivered(ctx context.Context, eventID string) error {
|
||||||
|
return s.repository.MarkActionConfirmOutboxDelivered(ctx, eventID, s.now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) MarkOutboxRetryable(ctx context.Context, eventID string, cause error, nextRetryAtMS int64) error {
|
||||||
|
message := ""
|
||||||
|
if cause != nil {
|
||||||
|
message = cause.Error()
|
||||||
|
}
|
||||||
|
return s.repository.MarkActionConfirmOutboxRetryable(ctx, eventID, message, nextRetryAtMS, s.now().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) requireRepository() error {
|
||||||
|
if s == nil || s.repository == nil {
|
||||||
|
return xerr.New(xerr.Unavailable, "action confirm repository is not configured")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmService) workerID() string {
|
||||||
|
nodeID := strings.TrimSpace(s.cfg.NodeID)
|
||||||
|
if nodeID == "" {
|
||||||
|
nodeID = "activity"
|
||||||
|
}
|
||||||
|
return "message-action-confirm-" + nodeID
|
||||||
|
}
|
||||||
|
|
||||||
|
type confirmIMPayload struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
ConfirmID string `json:"confirm_id"`
|
||||||
|
BusinessType string `json:"business_type"`
|
||||||
|
BusinessSubtype string `json:"business_subtype"`
|
||||||
|
SenderUserID string `json:"sender_user_id"`
|
||||||
|
ReceiverUserID string `json:"receiver_user_id"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Actions []confirmIMAction `json:"actions"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type confirmIMAction struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirmIMPayloadJSON(confirmID string, event messagedomain.RoleInvitationConfirmEvent, messageText string, createdAtMS int64) (string, error) {
|
||||||
|
payload := confirmIMPayload{
|
||||||
|
Type: "im_confirm",
|
||||||
|
Version: 1,
|
||||||
|
ConfirmID: confirmID,
|
||||||
|
BusinessType: messagedomain.ActionConfirmBusinessRoleInvitation,
|
||||||
|
BusinessSubtype: event.InvitationType,
|
||||||
|
SenderUserID: strconv.FormatInt(event.InviterUserID, 10),
|
||||||
|
ReceiverUserID: strconv.FormatInt(event.TargetUserID, 10),
|
||||||
|
Msg: messageText,
|
||||||
|
Actions: []confirmIMAction{
|
||||||
|
{Action: messagedomain.ActionConfirmActionReject, Text: "Reject"},
|
||||||
|
{Action: messagedomain.ActionConfirmActionAccept, Text: "Accept"},
|
||||||
|
},
|
||||||
|
CreatedAtMS: createdAtMS,
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(raw), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleInvitationMessageText(event messagedomain.RoleInvitationConfirmEvent) string {
|
||||||
|
name := firstNonEmptyText(event.Inviter.Username, event.Inviter.DisplayUserID, strconv.FormatInt(event.InviterUserID, 10))
|
||||||
|
agencyName := firstNonEmptyText(event.AgencyName, "guild")
|
||||||
|
switch event.InvitationType {
|
||||||
|
case "host":
|
||||||
|
return fmt.Sprintf("%s has invited you to join the %s guild", name, agencyName)
|
||||||
|
case "agency":
|
||||||
|
return fmt.Sprintf("%s has invited you to create the %s guild", name, agencyName)
|
||||||
|
case "bd":
|
||||||
|
return fmt.Sprintf("%s has invited you to become BD", name)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%s has sent you a confirmation request", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func confirmStatusFromRoleInvitation(status string) string {
|
||||||
|
switch strings.TrimSpace(status) {
|
||||||
|
case "accepted":
|
||||||
|
return messagedomain.ActionConfirmStatusAccepted
|
||||||
|
case "rejected":
|
||||||
|
return messagedomain.ActionConfirmStatusRejected
|
||||||
|
case "cancelled":
|
||||||
|
return messagedomain.ActionConfirmStatusCanceled
|
||||||
|
case "expired":
|
||||||
|
return messagedomain.ActionConfirmStatusExpired
|
||||||
|
default:
|
||||||
|
return messagedomain.ActionConfirmStatusPending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionFromRoleInvitationStatus(status string, fallback string) string {
|
||||||
|
switch strings.TrimSpace(status) {
|
||||||
|
case "accepted":
|
||||||
|
return messagedomain.ActionConfirmActionAccept
|
||||||
|
case "rejected":
|
||||||
|
return messagedomain.ActionConfirmActionReject
|
||||||
|
default:
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTerminalConfirmStatus(status string) bool {
|
||||||
|
switch status {
|
||||||
|
case messagedomain.ActionConfirmStatusAccepted, messagedomain.ActionConfirmStatusRejected, messagedomain.ActionConfirmStatusExpired, messagedomain.ActionConfirmStatusCanceled, messagedomain.ActionConfirmStatusFailed:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonEmptyText(values ...string) string {
|
||||||
|
for _, value := range values {
|
||||||
|
if value = strings.TrimSpace(value); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstPositiveInt64(values ...int64) int64 {
|
||||||
|
for _, value := range values {
|
||||||
|
if value > 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type actionConfirmCursor struct {
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
ConfirmID string `json:"confirm_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeActionConfirmCursor(confirm messagedomain.ActionConfirm) string {
|
||||||
|
raw, err := json.Marshal(actionConfirmCursor{CreatedAtMS: confirm.CreatedAtMS, ConfirmID: confirm.ConfirmID})
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return base64.RawURLEncoding.EncodeToString(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeActionConfirmCursor(value string) (actionConfirmCursor, error) {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
return actionConfirmCursor{}, nil
|
||||||
|
}
|
||||||
|
raw, err := base64.RawURLEncoding.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return actionConfirmCursor{}, xerr.New(xerr.PageTokenInvalid, "page token is invalid")
|
||||||
|
}
|
||||||
|
var cursor actionConfirmCursor
|
||||||
|
if err := json.Unmarshal(raw, &cursor); err != nil {
|
||||||
|
return actionConfirmCursor{}, xerr.New(xerr.PageTokenInvalid, "page token is invalid")
|
||||||
|
}
|
||||||
|
if cursor.CreatedAtMS <= 0 || strings.TrimSpace(cursor.ConfirmID) == "" {
|
||||||
|
return actionConfirmCursor{}, xerr.New(xerr.PageTokenInvalid, "page token is invalid")
|
||||||
|
}
|
||||||
|
return cursor, nil
|
||||||
|
}
|
||||||
@ -0,0 +1,387 @@
|
|||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
messagedomain "hyapp/services/activity-service/internal/domain/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveActionConfirmWithOutbox creates the confirm row and IM outbox in one transaction.
|
||||||
|
func (r *Repository) SaveActionConfirmWithOutbox(ctx context.Context, confirm messagedomain.ActionConfirm, outbox messagedomain.ActionConfirmOutbox) (bool, messagedomain.ActionConfirm, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return false, messagedomain.ActionConfirm{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||||
|
}
|
||||||
|
app := appcode.FromContext(ctx)
|
||||||
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
confirm.AppCode = app
|
||||||
|
outbox.AppCode = app
|
||||||
|
_, err = tx.ExecContext(ctx, `
|
||||||
|
INSERT INTO message_action_confirms (
|
||||||
|
app_code, confirm_id, source_name, source_event_id, business_type, business_subtype, business_id,
|
||||||
|
sender_user_id, receiver_user_id, message_text, status, action, command_id, processed_at_ms,
|
||||||
|
locked_by, lock_until_ms, error_message, metadata_json, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', 0, '', 0, '', CAST(? AS JSON), ?, ?)`,
|
||||||
|
app, confirm.ConfirmID, confirm.SourceName, confirm.SourceEventID, confirm.BusinessType, confirm.BusinessSubtype, confirm.BusinessID,
|
||||||
|
confirm.SenderUserID, confirm.ReceiverUserID, confirm.MessageText, confirm.Status, nullJSONText(confirm.MetadataJSON), confirm.CreatedAtMS, confirm.UpdatedAtMS,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if isMySQLDuplicate(err) {
|
||||||
|
existing, existingErr := getActionConfirmBySource(ctx, tx, confirm.SourceName, confirm.SourceEventID)
|
||||||
|
return false, existing, existingErr
|
||||||
|
}
|
||||||
|
return false, messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
_, err = tx.ExecContext(ctx, `
|
||||||
|
INSERT INTO message_action_outbox (
|
||||||
|
app_code, event_id, confirm_id, event_type, status, worker_id, lock_until_ms,
|
||||||
|
retry_count, next_retry_at_ms, last_error, payload_json, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (?, ?, ?, ?, ?, '', 0, 0, 0, '', CAST(? AS JSON), ?, ?)`,
|
||||||
|
app, outbox.EventID, confirm.ConfirmID, outbox.EventType, outbox.Status, outbox.PayloadJSON, outbox.CreatedAtMS, outbox.UpdatedAtMS,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if isMySQLDuplicate(err) {
|
||||||
|
existing, existingErr := getActionConfirmBySource(ctx, tx, confirm.SourceName, confirm.SourceEventID)
|
||||||
|
return false, existing, existingErr
|
||||||
|
}
|
||||||
|
return false, messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return false, messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
return true, confirm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimActionConfirm moves a pending or expired-processing confirm into processing under row lock.
|
||||||
|
func (r *Repository) ClaimActionConfirm(ctx context.Context, confirmID string, actorUserID int64, action string, commandID string, workerID string, nowMS int64, lockUntilMS int64) (messagedomain.ActionConfirm, bool, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||||
|
}
|
||||||
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
confirm, err := getActionConfirmByIDForUpdate(ctx, tx, confirmID)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
if confirm.ReceiverUserID != actorUserID {
|
||||||
|
return messagedomain.ActionConfirm{}, false, xerr.New(xerr.PermissionDenied, "action confirm is not visible to actor")
|
||||||
|
}
|
||||||
|
if isActionConfirmTerminal(confirm.Status) {
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
return confirm, false, nil
|
||||||
|
}
|
||||||
|
if confirm.Status == messagedomain.ActionConfirmStatusProcessing && confirm.LockUntilMS > nowMS {
|
||||||
|
return messagedomain.ActionConfirm{}, false, xerr.New(xerr.Conflict, "action confirm is processing")
|
||||||
|
}
|
||||||
|
_, err = tx.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_confirms
|
||||||
|
SET status = ?, action = '', command_id = ?, locked_by = ?, lock_until_ms = ?, error_message = '', updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND confirm_id = ?`,
|
||||||
|
messagedomain.ActionConfirmStatusProcessing, commandID, workerID, lockUntilMS, nowMS, appcode.FromContext(ctx), confirmID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, false, err
|
||||||
|
}
|
||||||
|
confirm.Status = messagedomain.ActionConfirmStatusProcessing
|
||||||
|
confirm.CommandID = commandID
|
||||||
|
confirm.LockedBy = workerID
|
||||||
|
confirm.LockUntilMS = lockUntilMS
|
||||||
|
confirm.UpdatedAtMS = nowMS
|
||||||
|
return confirm, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompleteActionConfirm writes the terminal state after the owner service accepts the business command.
|
||||||
|
func (r *Repository) CompleteActionConfirm(ctx context.Context, confirmID string, commandID string, status string, action string, processedAtMS int64) (messagedomain.ActionConfirm, error) {
|
||||||
|
result, err := r.db.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_confirms
|
||||||
|
SET status = ?, action = ?, processed_at_ms = ?, locked_by = '', lock_until_ms = 0, error_message = '', updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND confirm_id = ? AND command_id = ?`,
|
||||||
|
status, action, processedAtMS, processedAtMS, appcode.FromContext(ctx), confirmID, commandID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, err
|
||||||
|
}
|
||||||
|
if affected, err := result.RowsAffected(); err != nil {
|
||||||
|
return messagedomain.ActionConfirm{}, err
|
||||||
|
} else if affected == 0 {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.Conflict, "action confirm processing lock is lost")
|
||||||
|
}
|
||||||
|
return r.getActionConfirmByID(ctx, confirmID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseActionConfirm returns a failed processing attempt to pending so the user can retry with a new command.
|
||||||
|
func (r *Repository) ReleaseActionConfirm(ctx context.Context, confirmID string, commandID string, errorMessage string, nowMS int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_confirms
|
||||||
|
SET status = ?, locked_by = '', lock_until_ms = 0, error_message = ?, updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND confirm_id = ? AND command_id = ? AND status = ?`,
|
||||||
|
messagedomain.ActionConfirmStatusPending, truncateMessageActionError(errorMessage), nowMS, appcode.FromContext(ctx), confirmID, commandID, messagedomain.ActionConfirmStatusProcessing,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchGetActionConfirms returns current-user-visible confirm rows for a bounded ID list.
|
||||||
|
func (r *Repository) BatchGetActionConfirms(ctx context.Context, userID int64, confirmIDs []string) ([]messagedomain.ActionConfirm, error) {
|
||||||
|
if len(confirmIDs) == 0 {
|
||||||
|
return []messagedomain.ActionConfirm{}, nil
|
||||||
|
}
|
||||||
|
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(confirmIDs)), ",")
|
||||||
|
args := []any{appcode.FromContext(ctx), userID}
|
||||||
|
for _, confirmID := range confirmIDs {
|
||||||
|
args = append(args, confirmID)
|
||||||
|
}
|
||||||
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT %s
|
||||||
|
FROM message_action_confirms
|
||||||
|
WHERE app_code = ? AND receiver_user_id = ? AND confirm_id IN (`+placeholders+`)
|
||||||
|
ORDER BY created_at_ms DESC, confirm_id DESC`, actionConfirmColumns), args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return scanActionConfirmRows(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConversationActionConfirms reads one cursor page for confirms sent by a specific peer.
|
||||||
|
func (r *Repository) ListConversationActionConfirms(ctx context.Context, userID int64, peerUserID int64, limit int, cursorCreatedAtMS int64, cursorConfirmID string) ([]messagedomain.ActionConfirm, error) {
|
||||||
|
args := []any{appcode.FromContext(ctx), userID, peerUserID}
|
||||||
|
cursorSQL := ""
|
||||||
|
if cursorCreatedAtMS > 0 || cursorConfirmID != "" {
|
||||||
|
cursorSQL = " AND (created_at_ms < ? OR (created_at_ms = ? AND confirm_id < ?))"
|
||||||
|
args = append(args, cursorCreatedAtMS, cursorCreatedAtMS, cursorConfirmID)
|
||||||
|
}
|
||||||
|
args = append(args, limit)
|
||||||
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT %s
|
||||||
|
FROM message_action_confirms
|
||||||
|
WHERE app_code = ? AND receiver_user_id = ? AND sender_user_id = ?`+cursorSQL+`
|
||||||
|
ORDER BY created_at_ms DESC, confirm_id DESC
|
||||||
|
LIMIT ?`, actionConfirmColumns), args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
return scanActionConfirmRows(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimPendingActionConfirmOutbox locks pending/retryable confirm IM facts for RocketMQ publishing.
|
||||||
|
func (r *Repository) ClaimPendingActionConfirmOutbox(ctx context.Context, workerID string, nowMS int64, lockUntilMS int64, batchSize int) ([]messagedomain.ActionConfirmOutbox, error) {
|
||||||
|
if batchSize <= 0 {
|
||||||
|
batchSize = 100
|
||||||
|
}
|
||||||
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
records := make([]messagedomain.ActionConfirmOutbox, 0, batchSize)
|
||||||
|
branches := []struct {
|
||||||
|
query string
|
||||||
|
args []any
|
||||||
|
}{
|
||||||
|
{query: actionOutboxClaimQuery("status = 'pending' AND next_retry_at_ms <= ?"), args: []any{appcode.FromContext(ctx), nowMS}},
|
||||||
|
{query: actionOutboxClaimQuery("status = 'retryable' AND next_retry_at_ms <= ?"), args: []any{appcode.FromContext(ctx), nowMS}},
|
||||||
|
{query: actionOutboxClaimQuery("status = 'running' AND lock_until_ms <= ?"), args: []any{appcode.FromContext(ctx), nowMS}},
|
||||||
|
}
|
||||||
|
for _, branch := range branches {
|
||||||
|
remaining := batchSize - len(records)
|
||||||
|
if remaining <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args := append(append([]any{}, branch.args...), remaining)
|
||||||
|
items, err := queryActionConfirmOutboxRows(ctx, tx, branch.query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
records = append(records, items...)
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
if _, err := tx.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_outbox
|
||||||
|
SET status = 'running', worker_id = ?, lock_until_ms = ?, updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND event_id = ?`,
|
||||||
|
workerID, lockUntilMS, nowMS, record.AppCode, record.EventID,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for index := range records {
|
||||||
|
records[index].Status = "running"
|
||||||
|
records[index].WorkerID = workerID
|
||||||
|
records[index].LockUntilMS = lockUntilMS
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkActionConfirmOutboxDelivered records that RocketMQ accepted the confirm IM fact.
|
||||||
|
func (r *Repository) MarkActionConfirmOutboxDelivered(ctx context.Context, eventID string, nowMS int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_outbox
|
||||||
|
SET status = 'delivered', worker_id = '', lock_until_ms = 0, last_error = '', updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND event_id = ?`,
|
||||||
|
nowMS, appcode.FromContext(ctx), eventID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkActionConfirmOutboxRetryable releases a failed publish with a bounded retry delay.
|
||||||
|
func (r *Repository) MarkActionConfirmOutboxRetryable(ctx context.Context, eventID string, lastErr string, nextRetryAtMS int64, nowMS int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, `
|
||||||
|
UPDATE message_action_outbox
|
||||||
|
SET status = 'retryable', worker_id = '', lock_until_ms = 0, retry_count = retry_count + 1,
|
||||||
|
next_retry_at_ms = ?, last_error = ?, updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND event_id = ? AND status = 'running'`,
|
||||||
|
nextRetryAtMS, truncateMessageActionError(lastErr), nowMS, appcode.FromContext(ctx), eventID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionConfirmColumns = `
|
||||||
|
app_code, confirm_id, source_name, source_event_id, business_type, business_subtype, business_id,
|
||||||
|
sender_user_id, receiver_user_id, message_text, status, action, command_id, processed_at_ms,
|
||||||
|
locked_by, lock_until_ms, error_message, COALESCE(CAST(metadata_json AS CHAR), ''), created_at_ms, updated_at_ms`
|
||||||
|
|
||||||
|
func (r *Repository) getActionConfirmByID(ctx context.Context, confirmID string) (messagedomain.ActionConfirm, error) {
|
||||||
|
return scanActionConfirm(r.db.QueryRowContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT %s FROM message_action_confirms WHERE app_code = ? AND confirm_id = ?`,
|
||||||
|
actionConfirmColumns), appcode.FromContext(ctx), confirmID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionConfirmByIDForUpdate(ctx context.Context, tx *sql.Tx, confirmID string) (messagedomain.ActionConfirm, error) {
|
||||||
|
return scanActionConfirm(tx.QueryRowContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT %s FROM message_action_confirms WHERE app_code = ? AND confirm_id = ? FOR UPDATE`,
|
||||||
|
actionConfirmColumns), appcode.FromContext(ctx), confirmID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActionConfirmBySource(ctx context.Context, tx *sql.Tx, sourceName string, sourceEventID string) (messagedomain.ActionConfirm, error) {
|
||||||
|
return scanActionConfirm(tx.QueryRowContext(ctx, fmt.Sprintf(`
|
||||||
|
SELECT %s FROM message_action_confirms WHERE app_code = ? AND source_name = ? AND source_event_id = ?`,
|
||||||
|
actionConfirmColumns), appcode.FromContext(ctx), sourceName, sourceEventID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanActionConfirm(scanner inboxScanner) (messagedomain.ActionConfirm, error) {
|
||||||
|
var confirm messagedomain.ActionConfirm
|
||||||
|
err := scanner.Scan(
|
||||||
|
&confirm.AppCode,
|
||||||
|
&confirm.ConfirmID,
|
||||||
|
&confirm.SourceName,
|
||||||
|
&confirm.SourceEventID,
|
||||||
|
&confirm.BusinessType,
|
||||||
|
&confirm.BusinessSubtype,
|
||||||
|
&confirm.BusinessID,
|
||||||
|
&confirm.SenderUserID,
|
||||||
|
&confirm.ReceiverUserID,
|
||||||
|
&confirm.MessageText,
|
||||||
|
&confirm.Status,
|
||||||
|
&confirm.Action,
|
||||||
|
&confirm.CommandID,
|
||||||
|
&confirm.ProcessedAtMS,
|
||||||
|
&confirm.LockedBy,
|
||||||
|
&confirm.LockUntilMS,
|
||||||
|
&confirm.ErrorMessage,
|
||||||
|
&confirm.MetadataJSON,
|
||||||
|
&confirm.CreatedAtMS,
|
||||||
|
&confirm.UpdatedAtMS,
|
||||||
|
)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return messagedomain.ActionConfirm{}, xerr.New(xerr.NotFound, "action confirm not found")
|
||||||
|
}
|
||||||
|
return confirm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanActionConfirmRows(rows *sql.Rows) ([]messagedomain.ActionConfirm, error) {
|
||||||
|
items := make([]messagedomain.ActionConfirm, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
item, err := scanActionConfirm(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return items, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionOutboxClaimQuery(condition string) string {
|
||||||
|
return `
|
||||||
|
SELECT app_code, event_id, confirm_id, event_type, status, worker_id, lock_until_ms,
|
||||||
|
retry_count, next_retry_at_ms, last_error, CAST(payload_json AS CHAR), created_at_ms, updated_at_ms
|
||||||
|
FROM message_action_outbox
|
||||||
|
WHERE app_code = ? AND ` + condition + `
|
||||||
|
ORDER BY created_at_ms ASC, event_id ASC
|
||||||
|
LIMIT ? FOR UPDATE SKIP LOCKED`
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryActionConfirmOutboxRows(ctx context.Context, tx *sql.Tx, query string, args ...any) ([]messagedomain.ActionConfirmOutbox, error) {
|
||||||
|
rows, err := tx.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
records := make([]messagedomain.ActionConfirmOutbox, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var record messagedomain.ActionConfirmOutbox
|
||||||
|
if err := rows.Scan(
|
||||||
|
&record.AppCode,
|
||||||
|
&record.EventID,
|
||||||
|
&record.ConfirmID,
|
||||||
|
&record.EventType,
|
||||||
|
&record.Status,
|
||||||
|
&record.WorkerID,
|
||||||
|
&record.LockUntilMS,
|
||||||
|
&record.RetryCount,
|
||||||
|
&record.NextRetryAtMS,
|
||||||
|
&record.LastError,
|
||||||
|
&record.PayloadJSON,
|
||||||
|
&record.CreatedAtMS,
|
||||||
|
&record.UpdatedAtMS,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
records = append(records, record)
|
||||||
|
}
|
||||||
|
return records, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isActionConfirmTerminal(status string) bool {
|
||||||
|
switch status {
|
||||||
|
case messagedomain.ActionConfirmStatusAccepted, messagedomain.ActionConfirmStatusRejected, messagedomain.ActionConfirmStatusExpired, messagedomain.ActionConfirmStatusCanceled, messagedomain.ActionConfirmStatusFailed:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullJSONText(value string) any {
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncateMessageActionError(message string) string {
|
||||||
|
message = strings.TrimSpace(message)
|
||||||
|
if len(message) <= 512 {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
return message[:512]
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
activityv1 "hyapp.local/api/proto/activity/v1"
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
messagedomain "hyapp/services/activity-service/internal/domain/message"
|
||||||
|
messageservice "hyapp/services/activity-service/internal/service/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionConfirmServer adapts generic IM action confirmations to gRPC.
|
||||||
|
type ActionConfirmServer struct {
|
||||||
|
activityv1.UnimplementedMessageActionConfirmServiceServer
|
||||||
|
|
||||||
|
svc *messageservice.ActionConfirmService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActionConfirmServer(svc *messageservice.ActionConfirmService) *ActionConfirmServer {
|
||||||
|
return &ActionConfirmServer{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmServer) AcceptActionConfirm(ctx context.Context, req *activityv1.AcceptActionConfirmRequest) (*activityv1.AcceptActionConfirmResponse, error) {
|
||||||
|
if s.svc == nil {
|
||||||
|
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "action confirm service is not configured"))
|
||||||
|
}
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
confirm, err := s.svc.AcceptActionConfirm(ctx, req.GetActorUserId(), req.GetConfirmId(), req.GetCommandId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &activityv1.AcceptActionConfirmResponse{Confirm: actionConfirmProto(confirm)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmServer) RejectActionConfirm(ctx context.Context, req *activityv1.RejectActionConfirmRequest) (*activityv1.RejectActionConfirmResponse, error) {
|
||||||
|
if s.svc == nil {
|
||||||
|
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "action confirm service is not configured"))
|
||||||
|
}
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
confirm, err := s.svc.RejectActionConfirm(ctx, req.GetActorUserId(), req.GetConfirmId(), req.GetCommandId(), req.GetReason())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
return &activityv1.RejectActionConfirmResponse{Confirm: actionConfirmProto(confirm)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmServer) BatchGetActionConfirmStatus(ctx context.Context, req *activityv1.BatchGetActionConfirmStatusRequest) (*activityv1.BatchGetActionConfirmStatusResponse, error) {
|
||||||
|
if s.svc == nil {
|
||||||
|
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "action confirm service is not configured"))
|
||||||
|
}
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
items, err := s.svc.BatchGetActionConfirmStatus(ctx, req.GetUserId(), req.GetConfirmIds())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
resp := &activityv1.BatchGetActionConfirmStatusResponse{Items: make([]*activityv1.ActionConfirm, 0, len(items))}
|
||||||
|
for _, item := range items {
|
||||||
|
resp.Items = append(resp.Items, actionConfirmProto(item))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionConfirmServer) ListConversationActionConfirms(ctx context.Context, req *activityv1.ListConversationActionConfirmsRequest) (*activityv1.ListConversationActionConfirmsResponse, error) {
|
||||||
|
if s.svc == nil {
|
||||||
|
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "action confirm service is not configured"))
|
||||||
|
}
|
||||||
|
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||||||
|
items, nextPageToken, err := s.svc.ListConversationActionConfirms(ctx, req.GetUserId(), req.GetPeerUserId(), req.GetPageSize(), req.GetPageToken())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerr.ToGRPCError(err)
|
||||||
|
}
|
||||||
|
resp := &activityv1.ListConversationActionConfirmsResponse{Items: make([]*activityv1.ActionConfirm, 0, len(items)), NextPageToken: nextPageToken}
|
||||||
|
for _, item := range items {
|
||||||
|
resp.Items = append(resp.Items, actionConfirmProto(item))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionConfirmProto(confirm messagedomain.ActionConfirm) *activityv1.ActionConfirm {
|
||||||
|
return &activityv1.ActionConfirm{
|
||||||
|
ConfirmId: confirm.ConfirmID,
|
||||||
|
BusinessType: confirm.BusinessType,
|
||||||
|
BusinessSubtype: confirm.BusinessSubtype,
|
||||||
|
BusinessId: confirm.BusinessID,
|
||||||
|
SenderUserId: confirm.SenderUserID,
|
||||||
|
ReceiverUserId: confirm.ReceiverUserID,
|
||||||
|
Status: confirm.Status,
|
||||||
|
Action: confirm.Action,
|
||||||
|
ProcessedAtMs: confirm.ProcessedAtMS,
|
||||||
|
CreatedAtMs: confirm.CreatedAtMS,
|
||||||
|
UpdatedAtMs: confirm.UpdatedAtMS,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,11 +30,12 @@ func (s *CPWeeklyRankServer) GetCPWeeklyRankStatus(ctx context.Context, req *act
|
|||||||
return nil, xerr.ToGRPCError(err)
|
return nil, xerr.ToGRPCError(err)
|
||||||
}
|
}
|
||||||
return &activityv1.GetCPWeeklyRankStatusResponse{
|
return &activityv1.GetCPWeeklyRankStatusResponse{
|
||||||
Config: cpWeeklyRankConfigToProto(status.Config),
|
Config: cpWeeklyRankConfigToProto(status.Config),
|
||||||
Entries: cpWeeklyRankEntriesToProto(status.Entries),
|
Entries: cpWeeklyRankEntriesToProto(status.Entries),
|
||||||
PeriodStartMs: status.PeriodStartMS,
|
PreviousEntries: cpWeeklyRankEntriesToProto(status.PreviousEntries),
|
||||||
PeriodEndMs: status.PeriodEndMS,
|
PeriodStartMs: status.PeriodStartMS,
|
||||||
ServerTimeMs: status.ServerTimeMS,
|
PeriodEndMs: status.PeriodEndMS,
|
||||||
|
ServerTimeMs: status.ServerTimeMS,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -98,6 +98,7 @@ func New(cfg config.Config) (*App, error) {
|
|||||||
var appRegistryClient client.AppRegistryClient = client.NewGRPCAppRegistryClient(userConn)
|
var appRegistryClient client.AppRegistryClient = client.NewGRPCAppRegistryClient(userConn)
|
||||||
var walletClient client.WalletClient = client.NewGRPCWalletClient(walletConn)
|
var walletClient client.WalletClient = client.NewGRPCWalletClient(walletConn)
|
||||||
var messageClient client.MessageInboxClient = client.NewGRPCMessageInboxClient(activityConn)
|
var messageClient client.MessageInboxClient = client.NewGRPCMessageInboxClient(activityConn)
|
||||||
|
var messageActionClient client.MessageActionConfirmClient = client.NewGRPCMessageActionConfirmClient(activityConn)
|
||||||
var taskClient client.TaskClient = client.NewGRPCTaskClient(activityConn)
|
var taskClient client.TaskClient = client.NewGRPCTaskClient(activityConn)
|
||||||
var growthLevelClient client.GrowthLevelClient = client.NewGRPCGrowthLevelClient(activityConn)
|
var growthLevelClient client.GrowthLevelClient = client.NewGRPCGrowthLevelClient(activityConn)
|
||||||
var achievementClient client.AchievementClient = client.NewGRPCAchievementClient(activityConn)
|
var achievementClient client.AchievementClient = client.NewGRPCAchievementClient(activityConn)
|
||||||
@ -173,6 +174,7 @@ func New(cfg config.Config) (*App, error) {
|
|||||||
handler.SetAppRegistryClient(appRegistryClient)
|
handler.SetAppRegistryClient(appRegistryClient)
|
||||||
handler.SetWalletClient(walletClient)
|
handler.SetWalletClient(walletClient)
|
||||||
handler.SetMessageInboxClient(messageClient)
|
handler.SetMessageInboxClient(messageClient)
|
||||||
|
handler.SetMessageActionConfirmClient(messageActionClient)
|
||||||
handler.SetTaskClient(taskClient)
|
handler.SetTaskClient(taskClient)
|
||||||
handler.SetGrowthLevelClient(growthLevelClient)
|
handler.SetGrowthLevelClient(growthLevelClient)
|
||||||
handler.SetAchievementClient(achievementClient)
|
handler.SetAchievementClient(achievementClient)
|
||||||
|
|||||||
@ -16,15 +16,32 @@ type MessageInboxClient interface {
|
|||||||
DeleteInboxMessage(ctx context.Context, req *activityv1.DeleteInboxMessageRequest) (*activityv1.DeleteInboxMessageResponse, error)
|
DeleteInboxMessage(ctx context.Context, req *activityv1.DeleteInboxMessageRequest) (*activityv1.DeleteInboxMessageResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageActionConfirmClient abstracts gateway access to generic IM action confirmations.
|
||||||
|
type MessageActionConfirmClient interface {
|
||||||
|
AcceptActionConfirm(ctx context.Context, req *activityv1.AcceptActionConfirmRequest) (*activityv1.AcceptActionConfirmResponse, error)
|
||||||
|
RejectActionConfirm(ctx context.Context, req *activityv1.RejectActionConfirmRequest) (*activityv1.RejectActionConfirmResponse, error)
|
||||||
|
BatchGetActionConfirmStatus(ctx context.Context, req *activityv1.BatchGetActionConfirmStatusRequest) (*activityv1.BatchGetActionConfirmStatusResponse, error)
|
||||||
|
ListConversationActionConfirms(ctx context.Context, req *activityv1.ListConversationActionConfirmsRequest) (*activityv1.ListConversationActionConfirmsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
type grpcMessageInboxClient struct {
|
type grpcMessageInboxClient struct {
|
||||||
client activityv1.MessageInboxServiceClient
|
client activityv1.MessageInboxServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type grpcMessageActionConfirmClient struct {
|
||||||
|
client activityv1.MessageActionConfirmServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
// NewGRPCMessageInboxClient builds an inbox client backed by activity-service MessageInboxService.
|
// NewGRPCMessageInboxClient builds an inbox client backed by activity-service MessageInboxService.
|
||||||
func NewGRPCMessageInboxClient(conn *grpc.ClientConn) MessageInboxClient {
|
func NewGRPCMessageInboxClient(conn *grpc.ClientConn) MessageInboxClient {
|
||||||
return &grpcMessageInboxClient{client: activityv1.NewMessageInboxServiceClient(conn)}
|
return &grpcMessageInboxClient{client: activityv1.NewMessageInboxServiceClient(conn)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewGRPCMessageActionConfirmClient builds an action-confirm client backed by activity-service.
|
||||||
|
func NewGRPCMessageActionConfirmClient(conn *grpc.ClientConn) MessageActionConfirmClient {
|
||||||
|
return &grpcMessageActionConfirmClient{client: activityv1.NewMessageActionConfirmServiceClient(conn)}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcMessageInboxClient) ListMessageTabs(ctx context.Context, req *activityv1.ListMessageTabsRequest) (*activityv1.ListMessageTabsResponse, error) {
|
func (c *grpcMessageInboxClient) ListMessageTabs(ctx context.Context, req *activityv1.ListMessageTabsRequest) (*activityv1.ListMessageTabsResponse, error) {
|
||||||
return c.client.ListMessageTabs(ctx, req)
|
return c.client.ListMessageTabs(ctx, req)
|
||||||
}
|
}
|
||||||
@ -44,3 +61,19 @@ func (c *grpcMessageInboxClient) MarkInboxSectionRead(ctx context.Context, req *
|
|||||||
func (c *grpcMessageInboxClient) DeleteInboxMessage(ctx context.Context, req *activityv1.DeleteInboxMessageRequest) (*activityv1.DeleteInboxMessageResponse, error) {
|
func (c *grpcMessageInboxClient) DeleteInboxMessage(ctx context.Context, req *activityv1.DeleteInboxMessageRequest) (*activityv1.DeleteInboxMessageResponse, error) {
|
||||||
return c.client.DeleteInboxMessage(ctx, req)
|
return c.client.DeleteInboxMessage(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcMessageActionConfirmClient) AcceptActionConfirm(ctx context.Context, req *activityv1.AcceptActionConfirmRequest) (*activityv1.AcceptActionConfirmResponse, error) {
|
||||||
|
return c.client.AcceptActionConfirm(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcMessageActionConfirmClient) RejectActionConfirm(ctx context.Context, req *activityv1.RejectActionConfirmRequest) (*activityv1.RejectActionConfirmResponse, error) {
|
||||||
|
return c.client.RejectActionConfirm(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcMessageActionConfirmClient) BatchGetActionConfirmStatus(ctx context.Context, req *activityv1.BatchGetActionConfirmStatusRequest) (*activityv1.BatchGetActionConfirmStatusResponse, error) {
|
||||||
|
return c.client.BatchGetActionConfirmStatus(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcMessageActionConfirmClient) ListConversationActionConfirms(ctx context.Context, req *activityv1.ListConversationActionConfirmsRequest) (*activityv1.ListConversationActionConfirmsResponse, error) {
|
||||||
|
return c.client.ListConversationActionConfirms(ctx, req)
|
||||||
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ type UserProfileClient interface {
|
|||||||
UpdateUserProfile(ctx context.Context, req *userv1.UpdateUserProfileRequest) (*userv1.UpdateUserProfileResponse, error)
|
UpdateUserProfile(ctx context.Context, req *userv1.UpdateUserProfileRequest) (*userv1.UpdateUserProfileResponse, error)
|
||||||
UpdateUserProfileBackground(ctx context.Context, req *userv1.UpdateUserProfileBackgroundRequest) (*userv1.UpdateUserProfileBackgroundResponse, error)
|
UpdateUserProfileBackground(ctx context.Context, req *userv1.UpdateUserProfileBackgroundRequest) (*userv1.UpdateUserProfileBackgroundResponse, error)
|
||||||
ChangeUserCountry(ctx context.Context, req *userv1.ChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error)
|
ChangeUserCountry(ctx context.Context, req *userv1.ChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error)
|
||||||
|
AdminChangeUserCountry(ctx context.Context, req *userv1.AdminChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error)
|
||||||
CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error)
|
CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error)
|
||||||
ListManagerUserBlocks(ctx context.Context, req *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error)
|
ListManagerUserBlocks(ctx context.Context, req *userv1.ListManagerUserBlocksRequest) (*userv1.ListManagerUserBlocksResponse, error)
|
||||||
UnblockManagerUser(ctx context.Context, req *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error)
|
UnblockManagerUser(ctx context.Context, req *userv1.UnblockManagerUserRequest) (*userv1.UnblockManagerUserResponse, error)
|
||||||
@ -338,6 +339,10 @@ func (c *grpcUserProfileClient) ChangeUserCountry(ctx context.Context, req *user
|
|||||||
return c.client.ChangeUserCountry(ctx, req)
|
return c.client.ChangeUserCountry(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcUserProfileClient) AdminChangeUserCountry(ctx context.Context, req *userv1.AdminChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error) {
|
||||||
|
return c.client.AdminChangeUserCountry(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcUserProfileClient) CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
|
func (c *grpcUserProfileClient) CreateManagerUserBlock(ctx context.Context, req *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
|
||||||
return c.client.CreateManagerUserBlock(ctx, req)
|
return c.client.CreateManagerUserBlock(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,12 @@ type WalletClient interface {
|
|||||||
GetWalletValueSummary(ctx context.Context, req *walletv1.GetWalletValueSummaryRequest) (*walletv1.GetWalletValueSummaryResponse, error)
|
GetWalletValueSummary(ctx context.Context, req *walletv1.GetWalletValueSummaryRequest) (*walletv1.GetWalletValueSummaryResponse, error)
|
||||||
GetUserGiftWall(ctx context.Context, req *walletv1.GetUserGiftWallRequest) (*walletv1.GetUserGiftWallResponse, error)
|
GetUserGiftWall(ctx context.Context, req *walletv1.GetUserGiftWallRequest) (*walletv1.GetUserGiftWallResponse, error)
|
||||||
ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error)
|
ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error)
|
||||||
|
ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error)
|
||||||
ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error)
|
ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error)
|
||||||
CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
CreateH5RechargeOrder(ctx context.Context, req *walletv1.CreateH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
|
CreateTemporaryRechargeOrder(ctx context.Context, req *walletv1.CreateTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
|
GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
|
ListTemporaryRechargeOrders(ctx context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error)
|
||||||
SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error)
|
SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
GetH5RechargeOrder(ctx context.Context, req *walletv1.GetH5RechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
|
||||||
HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error)
|
HandleMifapayNotify(ctx context.Context, req *walletv1.HandleMifapayNotifyRequest) (*walletv1.HandleMifapayNotifyResponse, error)
|
||||||
@ -92,6 +96,10 @@ func (c *grpcWalletClient) ListRechargeProducts(ctx context.Context, req *wallet
|
|||||||
return c.client.ListRechargeProducts(ctx, req)
|
return c.client.ListRechargeProducts(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcWalletClient) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
|
||||||
|
return c.client.ListThirdPartyPaymentChannels(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcWalletClient) ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) {
|
func (c *grpcWalletClient) ListH5RechargeOptions(ctx context.Context, req *walletv1.H5RechargeOptionsRequest) (*walletv1.H5RechargeOptionsResponse, error) {
|
||||||
return c.client.ListH5RechargeOptions(ctx, req)
|
return c.client.ListH5RechargeOptions(ctx, req)
|
||||||
}
|
}
|
||||||
@ -100,6 +108,18 @@ func (c *grpcWalletClient) CreateH5RechargeOrder(ctx context.Context, req *walle
|
|||||||
return c.client.CreateH5RechargeOrder(ctx, req)
|
return c.client.CreateH5RechargeOrder(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *grpcWalletClient) CreateTemporaryRechargeOrder(ctx context.Context, req *walletv1.CreateTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
||||||
|
return c.client.CreateTemporaryRechargeOrder(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcWalletClient) GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
||||||
|
return c.client.GetTemporaryRechargeOrder(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *grpcWalletClient) ListTemporaryRechargeOrders(ctx context.Context, req *walletv1.ListTemporaryRechargeOrdersRequest) (*walletv1.ListTemporaryRechargeOrdersResponse, error) {
|
||||||
|
return c.client.ListTemporaryRechargeOrders(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *grpcWalletClient) SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
func (c *grpcWalletClient) SubmitH5RechargeTx(ctx context.Context, req *walletv1.SubmitH5RechargeTxRequest) (*walletv1.H5RechargeOrderResponse, error) {
|
||||||
return c.client.SubmitH5RechargeTx(ctx, req)
|
return c.client.SubmitH5RechargeTx(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type cpWeeklyRankStatusData struct {
|
type cpWeeklyRankStatusData struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Period cpWeeklyRankPeriodData `json:"period"`
|
Period cpWeeklyRankPeriodData `json:"period"`
|
||||||
Rewards []cpWeeklyRankRewardData `json:"rewards"`
|
Rewards []cpWeeklyRankRewardData `json:"rewards"`
|
||||||
Entries []cpWeeklyRankEntryData `json:"entries"`
|
Entries []cpWeeklyRankEntryData `json:"entries"`
|
||||||
ServerTimeMS int64 `json:"server_time_ms"`
|
PreviousEntries []cpWeeklyRankEntryData `json:"previous_entries"`
|
||||||
RelationType string `json:"relation_type"`
|
ServerTimeMS int64 `json:"server_time_ms"`
|
||||||
|
RelationType string `json:"relation_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cpWeeklyRankPeriodData struct {
|
type cpWeeklyRankPeriodData struct {
|
||||||
@ -81,12 +82,13 @@ func (h *Handler) getCPWeeklyRankStatus(writer http.ResponseWriter, request *htt
|
|||||||
func cpWeeklyRankStatusFromProto(resp *activityv1.GetCPWeeklyRankStatusResponse, groups map[int64]checkinResourceGroupData) cpWeeklyRankStatusData {
|
func cpWeeklyRankStatusFromProto(resp *activityv1.GetCPWeeklyRankStatusResponse, groups map[int64]checkinResourceGroupData) cpWeeklyRankStatusData {
|
||||||
config := resp.GetConfig()
|
config := resp.GetConfig()
|
||||||
return cpWeeklyRankStatusData{
|
return cpWeeklyRankStatusData{
|
||||||
Enabled: config.GetEnabled(),
|
Enabled: config.GetEnabled(),
|
||||||
RelationType: config.GetRelationType(),
|
RelationType: config.GetRelationType(),
|
||||||
Period: cpWeeklyRankPeriodData{StartMS: resp.GetPeriodStartMs(), EndMS: resp.GetPeriodEndMs()},
|
Period: cpWeeklyRankPeriodData{StartMS: resp.GetPeriodStartMs(), EndMS: resp.GetPeriodEndMs()},
|
||||||
Rewards: cpWeeklyRankRewardsFromProto(config.GetRewards(), groups),
|
Rewards: cpWeeklyRankRewardsFromProto(config.GetRewards(), groups),
|
||||||
Entries: cpWeeklyRankEntriesFromProto(resp.GetEntries()),
|
Entries: cpWeeklyRankEntriesFromProto(resp.GetEntries()),
|
||||||
ServerTimeMS: resp.GetServerTimeMs(),
|
PreviousEntries: cpWeeklyRankEntriesFromProto(resp.GetPreviousEntries()),
|
||||||
|
ServerTimeMS: resp.GetServerTimeMs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -141,6 +141,10 @@ func (f *fakeInviteActivityUserProfileClient) ChangeUserCountry(context.Context,
|
|||||||
return &userv1.ChangeUserCountryResponse{}, nil
|
return &userv1.ChangeUserCountryResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeInviteActivityUserProfileClient) AdminChangeUserCountry(context.Context, *userv1.AdminChangeUserCountryRequest) (*userv1.ChangeUserCountryResponse, error) {
|
||||||
|
return &userv1.ChangeUserCountryResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *fakeInviteActivityUserProfileClient) CreateManagerUserBlock(context.Context, *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
|
func (f *fakeInviteActivityUserProfileClient) CreateManagerUserBlock(context.Context, *userv1.CreateManagerUserBlockRequest) (*userv1.CreateManagerUserBlockResponse, error) {
|
||||||
return &userv1.CreateManagerUserBlockResponse{}, nil
|
return &userv1.CreateManagerUserBlockResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import (
|
|||||||
"hyapp/services/gateway-service/internal/appconfig"
|
"hyapp/services/gateway-service/internal/appconfig"
|
||||||
"hyapp/services/gateway-service/internal/auth"
|
"hyapp/services/gateway-service/internal/auth"
|
||||||
"hyapp/services/gateway-service/internal/transport/http/httpkit"
|
"hyapp/services/gateway-service/internal/transport/http/httpkit"
|
||||||
|
|
||||||
|
userv1 "hyapp.local/api/proto/user/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type appBootstrapBottomTab struct {
|
type appBootstrapBottomTab struct {
|
||||||
@ -128,13 +130,16 @@ func (h *Handler) listAppBanners(writer http.ResponseWriter, request *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
items, err := h.appConfigReader.ListBanners(request.Context(), appconfig.BannerQuery{
|
query := appconfig.BannerQuery{
|
||||||
AppCode: appcode.FromContext(request.Context()),
|
AppCode: appcode.FromContext(request.Context()),
|
||||||
DisplayScope: displayScope,
|
DisplayScope: displayScope,
|
||||||
Platform: httpkit.FirstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"),
|
Platform: httpkit.FirstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"),
|
||||||
RegionID: optionalInt64Query(request, "region_id"),
|
}
|
||||||
Country: httpkit.FirstQueryOrHeader(request, "country", "X-Country-Code", "X-App-Country"),
|
if !h.applyAuthenticatedBannerAudience(writer, request, &query) {
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := h.appConfigReader.ListBanners(request.Context(), query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
return
|
return
|
||||||
@ -143,6 +148,44 @@ func (h *Handler) listAppBanners(writer http.ResponseWriter, request *http.Reque
|
|||||||
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)})
|
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) applyAuthenticatedBannerAudience(writer http.ResponseWriter, request *http.Request, query *appconfig.BannerQuery) bool {
|
||||||
|
viewerUserID := auth.UserIDFromContext(request.Context())
|
||||||
|
if viewerUserID == 0 {
|
||||||
|
// banner 是公开接口,但未登录请求不能用 region_id/country 伪装区域身份;空区域只会命中全局 banner。
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if h.userProfileClient == nil {
|
||||||
|
// 登录态区域必须以 user-service 的用户事实为准,缺少依赖时不能退回客户端传参。
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
|
||||||
|
Meta: httpkit.UserMeta(request, ""),
|
||||||
|
UserId: viewerUserID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// user-service 查询失败时返回上游错误,避免用客户端 region_id/country 打开错误区域的 banner。
|
||||||
|
httpkit.WriteRPCError(writer, request, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
user := resp.GetUser()
|
||||||
|
if user == nil {
|
||||||
|
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if user.GetAppCode() != "" && appcode.Normalize(user.GetAppCode()) != query.AppCode {
|
||||||
|
// token 所属租户和用户资料租户不一致时拒绝返回区域配置,防止跨 App 读取 banner 策略。
|
||||||
|
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已登录用户的区域由后端用户资料覆盖;客户端保留 region_id/country 只做老版本参数兼容,不参与裁决。
|
||||||
|
query.RegionID = user.GetRegionId()
|
||||||
|
query.Country = strings.TrimSpace(user.GetCountry())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// listSplashScreens 返回后台 APP配置/开屏配置 中当前可见的开屏列表;客户端按列表顺序取最高优先级配置展示。
|
// listSplashScreens 返回后台 APP配置/开屏配置 中当前可见的开屏列表;客户端按列表顺序取最高优先级配置展示。
|
||||||
func (h *Handler) listSplashScreens(writer http.ResponseWriter, request *http.Request) {
|
func (h *Handler) listSplashScreens(writer http.ResponseWriter, request *http.Request) {
|
||||||
if h.appConfigReader == nil {
|
if h.appConfigReader == nil {
|
||||||
|
|||||||
@ -28,25 +28,28 @@ type ObjectUploader interface {
|
|||||||
// Handler owns app platform endpoints such as bootstrap, config, upload and device token binding.
|
// Handler owns app platform endpoints such as bootstrap, config, upload and device token binding.
|
||||||
// It does not own user identity or resource semantics; those remain in their owner services/packages.
|
// It does not own user identity or resource semantics; those remain in their owner services/packages.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
appConfigReader ConfigReader
|
appConfigReader ConfigReader
|
||||||
userAuthClient client.UserAuthClient
|
userAuthClient client.UserAuthClient
|
||||||
userDeviceClient client.UserDeviceClient
|
userProfileClient client.UserProfileClient
|
||||||
objectUploader ObjectUploader
|
userDeviceClient client.UserDeviceClient
|
||||||
|
objectUploader ObjectUploader
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppConfigReader ConfigReader
|
AppConfigReader ConfigReader
|
||||||
UserAuthClient client.UserAuthClient
|
UserAuthClient client.UserAuthClient
|
||||||
UserDeviceClient client.UserDeviceClient
|
UserProfileClient client.UserProfileClient
|
||||||
ObjectUploader ObjectUploader
|
UserDeviceClient client.UserDeviceClient
|
||||||
|
ObjectUploader ObjectUploader
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config Config) *Handler {
|
func New(config Config) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
appConfigReader: config.AppConfigReader,
|
appConfigReader: config.AppConfigReader,
|
||||||
userAuthClient: config.UserAuthClient,
|
userAuthClient: config.UserAuthClient,
|
||||||
userDeviceClient: config.UserDeviceClient,
|
userProfileClient: config.UserProfileClient,
|
||||||
objectUploader: config.ObjectUploader,
|
userDeviceClient: config.UserDeviceClient,
|
||||||
|
objectUploader: config.ObjectUploader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ type Handler struct {
|
|||||||
appRegistryClient client.AppRegistryClient
|
appRegistryClient client.AppRegistryClient
|
||||||
walletClient client.WalletClient
|
walletClient client.WalletClient
|
||||||
messageClient client.MessageInboxClient
|
messageClient client.MessageInboxClient
|
||||||
|
messageActionClient client.MessageActionConfirmClient
|
||||||
taskClient client.TaskClient
|
taskClient client.TaskClient
|
||||||
growthLevelClient client.GrowthLevelClient
|
growthLevelClient client.GrowthLevelClient
|
||||||
achievementClient client.AchievementClient
|
achievementClient client.AchievementClient
|
||||||
@ -213,6 +214,11 @@ func (h *Handler) SetMessageInboxClient(messageClient client.MessageInboxClient)
|
|||||||
h.messageClient = messageClient
|
h.messageClient = messageClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMessageActionConfirmClient 注入 activity-service action-confirm client。
|
||||||
|
func (h *Handler) SetMessageActionConfirmClient(messageActionClient client.MessageActionConfirmClient) {
|
||||||
|
h.messageActionClient = messageActionClient
|
||||||
|
}
|
||||||
|
|
||||||
// SetTaskClient 注入 activity-service task client。
|
// SetTaskClient 注入 activity-service task client。
|
||||||
func (h *Handler) SetTaskClient(taskClient client.TaskClient) {
|
func (h *Handler) SetTaskClient(taskClient client.TaskClient) {
|
||||||
h.taskClient = taskClient
|
h.taskClient = taskClient
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user