90 lines
3.7 KiB
Go
90 lines
3.7 KiB
Go
// Package walletclient 适配 wallet-service 资源与 VIP 权益 RPC,供 user-service 构建展示读模型并执行隐私门禁。
|
||
package walletclient
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
|
||
"google.golang.org/grpc"
|
||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/idgen"
|
||
cpdomain "hyapp/services/user-service/internal/domain/cp"
|
||
)
|
||
|
||
const avatarFrameResourceType = "avatar_frame"
|
||
|
||
// Client 只暴露 user-service 构建 CP 读模型和执行资料隐私需要的钱包读取能力。
|
||
type Client struct {
|
||
client walletv1.WalletServiceClient
|
||
}
|
||
|
||
// CheckVipBenefit 读取 wallet-service 已结合 App 配置、有效 VIP、体验卡排除项及用户开关计算出的最终结果。
|
||
// user-service 不缓存隐私权益,保证资料统计隐藏和匿名访问每次都按访问发生时的事实固化。
|
||
func (c *Client) CheckVipBenefit(ctx context.Context, requestID string, userID int64, benefitCode string) (bool, error) {
|
||
if c == nil || c.client == nil {
|
||
return false, grpc.ErrClientConnClosing
|
||
}
|
||
requestID = strings.TrimSpace(requestID)
|
||
if requestID == "" {
|
||
// 非 gateway 的内部调用缺 request_id 时生成兜底链路 ID;HTTP 请求始终复用 gateway 已生成的 request_id。
|
||
requestID = idgen.New("user_privacy")
|
||
}
|
||
resp, err := c.client.CheckVipBenefit(ctx, &walletv1.CheckVipBenefitRequest{
|
||
RequestId: requestID,
|
||
AppCode: appcode.FromContext(ctx),
|
||
UserId: userID,
|
||
BenefitCode: benefitCode,
|
||
})
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return resp.GetAllowed(), nil
|
||
}
|
||
|
||
// NewGRPC 基于共享 gRPC 连接创建 wallet-service 资源读取客户端。
|
||
func NewGRPC(conn grpc.ClientConnInterface) *Client {
|
||
return &Client{client: walletv1.NewWalletServiceClient(conn)}
|
||
}
|
||
|
||
// BatchGetAvatarFrames 返回用户当前佩戴头像框快照;没有佩戴的用户不会出现在结果 map 中。
|
||
func (c *Client) BatchGetAvatarFrames(ctx context.Context, appCode string, userIDs []int64) (map[int64]cpdomain.AvatarFrameSnapshot, error) {
|
||
if c == nil || c.client == nil || len(userIDs) == 0 {
|
||
// 没有用户或依赖未装配时返回空 map,调用方会保持 avatar_frame=nil;这里不制造默认头像框。
|
||
return map[int64]cpdomain.AvatarFrameSnapshot{}, nil
|
||
}
|
||
// wallet-service 是资源佩戴事实的 owner;user-service 只按用户批量读取 avatar_frame 类型,避免读取整包资源列表。
|
||
resp, err := c.client.BatchGetUserEquippedResources(ctx, &walletv1.BatchGetUserEquippedResourcesRequest{
|
||
RequestId: idgen.New("cp_frame"),
|
||
AppCode: appCode,
|
||
UserIds: userIDs,
|
||
ResourceTypes: []string{avatarFrameResourceType},
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
frames := make(map[int64]cpdomain.AvatarFrameSnapshot, len(resp.GetUsers()))
|
||
for _, user := range resp.GetUsers() {
|
||
for _, entitlement := range user.GetResources() {
|
||
resource := entitlement.GetResource()
|
||
if resource == nil || resource.GetResourceType() != avatarFrameResourceType || resource.GetResourceId() <= 0 {
|
||
// 返回包里可能混入空资源或未来扩展资源类型;榜单只认有效 avatar_frame,其他内容不能进入 CP 快照。
|
||
continue
|
||
}
|
||
// 一个用户同一类型理论上只应佩戴一个资源;命中第一条有效头像框后 break,避免后续异常重复覆盖。
|
||
frames[user.GetUserId()] = cpdomain.AvatarFrameSnapshot{
|
||
ResourceID: resource.GetResourceId(),
|
||
ResourceCode: resource.GetResourceCode(),
|
||
Name: resource.GetName(),
|
||
AssetURL: resource.GetAssetUrl(),
|
||
PreviewURL: resource.GetPreviewUrl(),
|
||
AnimationURL: resource.GetAnimationUrl(),
|
||
MetadataJSON: resource.GetMetadataJson(),
|
||
EntitlementID: entitlement.GetEntitlementId(),
|
||
}
|
||
break
|
||
}
|
||
}
|
||
return frames, nil
|
||
}
|