2026-07-17 16:55:22 +08:00

90 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 时生成兜底链路 IDHTTP 请求始终复用 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 是资源佩戴事实的 owneruser-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
}