Update wallet resources and admin host flows
This commit is contained in:
parent
6de77ee02d
commit
427d335e4a
@ -174,9 +174,10 @@ type DebitGiftResponse struct {
|
||||
BillingReceiptId string `protobuf:"bytes,1,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"`
|
||||
TransactionId string `protobuf:"bytes,2,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"`
|
||||
CoinSpent int64 `protobuf:"varint,3,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"`
|
||||
GiftPointAdded int64 `protobuf:"varint,4,opt,name=gift_point_added,json=giftPointAdded,proto3" json:"gift_point_added,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,5,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
PriceVersion string `protobuf:"bytes,6,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
|
||||
// gift_point_added 是历史回执字段;GIFT_POINT 已下线,新送礼固定返回 0。
|
||||
GiftPointAdded int64 `protobuf:"varint,4,opt,name=gift_point_added,json=giftPointAdded,proto3" json:"gift_point_added,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,5,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
PriceVersion string `protobuf:"bytes,6,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
|
||||
// balance_after 是 sender 被扣费资产 available_amount 账后余额。
|
||||
BalanceAfter int64 `protobuf:"varint,7,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,8,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
@ -2574,9 +2575,10 @@ type Resource struct {
|
||||
ManagerGrantEnabled bool `protobuf:"varint,21,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3" json:"manager_grant_enabled,omitempty"`
|
||||
PriceType string `protobuf:"bytes,22,opt,name=price_type,json=priceType,proto3" json:"price_type,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,23,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,24,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;资源和礼物价格现在只维护真实 COIN 价格,新写入固定为 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,24,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Resource) Reset() {
|
||||
@ -3053,20 +3055,21 @@ type GiftConfig struct {
|
||||
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"`
|
||||
PriceVersion string `protobuf:"bytes,9,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
CreatedByUserId int64 `protobuf:"varint,13,opt,name=created_by_user_id,json=createdByUserId,proto3" json:"created_by_user_id,omitempty"`
|
||||
UpdatedByUserId int64 `protobuf:"varint,14,opt,name=updated_by_user_id,json=updatedByUserId,proto3" json:"updated_by_user_id,omitempty"`
|
||||
CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
|
||||
UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,17,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,18,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,19,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,20,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,21,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,22,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;送礼结算不再读取该值,新写入固定为 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
CreatedByUserId int64 `protobuf:"varint,13,opt,name=created_by_user_id,json=createdByUserId,proto3" json:"created_by_user_id,omitempty"`
|
||||
UpdatedByUserId int64 `protobuf:"varint,14,opt,name=updated_by_user_id,json=updatedByUserId,proto3" json:"updated_by_user_id,omitempty"`
|
||||
CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
|
||||
UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,17,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,18,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,19,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,20,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,21,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,22,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GiftConfig) Reset() {
|
||||
@ -4307,9 +4310,10 @@ type CreateResourceRequest struct {
|
||||
ManagerGrantEnabled *bool `protobuf:"varint,18,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3,oneof" json:"manager_grant_enabled,omitempty"`
|
||||
PriceType string `protobuf:"bytes,19,opt,name=price_type,json=priceType,proto3" json:"price_type,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,20,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,21,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,21,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CreateResourceRequest) Reset() {
|
||||
@ -4512,9 +4516,10 @@ type UpdateResourceRequest struct {
|
||||
ManagerGrantEnabled *bool `protobuf:"varint,19,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3,oneof" json:"manager_grant_enabled,omitempty"`
|
||||
PriceType string `protobuf:"bytes,20,opt,name=price_type,json=priceType,proto3" json:"price_type,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,21,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,22,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,22,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UpdateResourceRequest) Reset() {
|
||||
@ -5424,6 +5429,7 @@ type ListGiftConfigsRequest struct {
|
||||
ActiveOnly bool `protobuf:"varint,7,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"`
|
||||
RegionId int64 `protobuf:"varint,8,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"`
|
||||
FilterRegion bool `protobuf:"varint,9,opt,name=filter_region,json=filterRegion,proto3" json:"filter_region,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,10,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -5521,6 +5527,13 @@ func (x *ListGiftConfigsRequest) GetFilterRegion() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ListGiftConfigsRequest) GetGiftTypeCode() string {
|
||||
if x != nil {
|
||||
return x.GiftTypeCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListGiftConfigsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Gifts []*GiftConfig `protobuf:"bytes,1,rep,name=gifts,proto3" json:"gifts,omitempty"`
|
||||
@ -5841,18 +5854,19 @@ type CreateGiftConfigRequest struct {
|
||||
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"`
|
||||
PriceVersion string `protobuf:"bytes,9,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
|
||||
OperatorUserId int64 `protobuf:"varint,14,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,15,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,16,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,17,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,18,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,19,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,20,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
|
||||
OperatorUserId int64 `protobuf:"varint,14,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,15,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,16,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,17,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,18,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,19,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,20,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CreateGiftConfigRequest) Reset() {
|
||||
@ -6037,18 +6051,19 @@ type UpdateGiftConfigRequest struct {
|
||||
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"`
|
||||
PriceVersion string `protobuf:"bytes,9,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
|
||||
CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
|
||||
OperatorUserId int64 `protobuf:"varint,14,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,15,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,16,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,17,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,18,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,19,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,20,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
|
||||
HeatValue int64 `protobuf:"varint,12,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"`
|
||||
EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
|
||||
OperatorUserId int64 `protobuf:"varint,14,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"`
|
||||
RegionIds []int64 `protobuf:"varint,15,rep,packed,name=region_ids,json=regionIds,proto3" json:"region_ids,omitempty"`
|
||||
GiftTypeCode string `protobuf:"bytes,16,opt,name=gift_type_code,json=giftTypeCode,proto3" json:"gift_type_code,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,17,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
EffectiveFromMs int64 `protobuf:"varint,18,opt,name=effective_from_ms,json=effectiveFromMs,proto3" json:"effective_from_ms,omitempty"`
|
||||
EffectiveToMs int64 `protobuf:"varint,19,opt,name=effective_to_ms,json=effectiveToMs,proto3" json:"effective_to_ms,omitempty"`
|
||||
EffectTypes []string `protobuf:"bytes,20,rep,name=effect_types,json=effectTypes,proto3" json:"effect_types,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UpdateGiftConfigRequest) Reset() {
|
||||
@ -8589,15 +8604,16 @@ type GiftWallItem struct {
|
||||
TotalValue int64 `protobuf:"varint,9,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"`
|
||||
TotalCoinValue int64 `protobuf:"varint,10,opt,name=total_coin_value,json=totalCoinValue,proto3" json:"total_coin_value,omitempty"`
|
||||
TotalDiamondValue int64 `protobuf:"varint,11,opt,name=total_diamond_value,json=totalDiamondValue,proto3" json:"total_diamond_value,omitempty"`
|
||||
TotalGiftPoint int64 `protobuf:"varint,12,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,omitempty"`
|
||||
TotalHeatValue int64 `protobuf:"varint,13,opt,name=total_heat_value,json=totalHeatValue,proto3" json:"total_heat_value,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,14,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
LastPriceVersion string `protobuf:"bytes,15,opt,name=last_price_version,json=lastPriceVersion,proto3" json:"last_price_version,omitempty"`
|
||||
FirstReceivedAtMs int64 `protobuf:"varint,16,opt,name=first_received_at_ms,json=firstReceivedAtMs,proto3" json:"first_received_at_ms,omitempty"`
|
||||
LastReceivedAtMs int64 `protobuf:"varint,17,opt,name=last_received_at_ms,json=lastReceivedAtMs,proto3" json:"last_received_at_ms,omitempty"`
|
||||
SortOrder int32 `protobuf:"varint,18,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// total_gift_point 是历史礼物墙字段;新送礼不再累加。
|
||||
TotalGiftPoint int64 `protobuf:"varint,12,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,omitempty"`
|
||||
TotalHeatValue int64 `protobuf:"varint,13,opt,name=total_heat_value,json=totalHeatValue,proto3" json:"total_heat_value,omitempty"`
|
||||
ChargeAssetType string `protobuf:"bytes,14,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
|
||||
LastPriceVersion string `protobuf:"bytes,15,opt,name=last_price_version,json=lastPriceVersion,proto3" json:"last_price_version,omitempty"`
|
||||
FirstReceivedAtMs int64 `protobuf:"varint,16,opt,name=first_received_at_ms,json=firstReceivedAtMs,proto3" json:"first_received_at_ms,omitempty"`
|
||||
LastReceivedAtMs int64 `protobuf:"varint,17,opt,name=last_received_at_ms,json=lastReceivedAtMs,proto3" json:"last_received_at_ms,omitempty"`
|
||||
SortOrder int32 `protobuf:"varint,18,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GiftWallItem) Reset() {
|
||||
@ -8824,10 +8840,11 @@ type GetUserGiftWallResponse struct {
|
||||
TotalValue int64 `protobuf:"varint,4,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"`
|
||||
TotalCoinValue int64 `protobuf:"varint,5,opt,name=total_coin_value,json=totalCoinValue,proto3" json:"total_coin_value,omitempty"`
|
||||
TotalDiamondValue int64 `protobuf:"varint,6,opt,name=total_diamond_value,json=totalDiamondValue,proto3" json:"total_diamond_value,omitempty"`
|
||||
TotalGiftPoint int64 `protobuf:"varint,7,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,omitempty"`
|
||||
TotalHeatValue int64 `protobuf:"varint,8,opt,name=total_heat_value,json=totalHeatValue,proto3" json:"total_heat_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
// total_gift_point 是历史礼物墙汇总字段;新送礼不再累加。
|
||||
TotalGiftPoint int64 `protobuf:"varint,7,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,omitempty"`
|
||||
TotalHeatValue int64 `protobuf:"varint,8,opt,name=total_heat_value,json=totalHeatValue,proto3" json:"total_heat_value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetUserGiftWallResponse) Reset() {
|
||||
@ -14606,7 +14623,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" +
|
||||
"\x06status\x18\x04 \x01(\tR\x06status\x12(\n" +
|
||||
"\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"M\n" +
|
||||
"\x15ResourceGroupResponse\x124\n" +
|
||||
"\x05group\x18\x01 \x01(\v2\x1e.hyapp.wallet.v1.ResourceGroupR\x05group\"\x98\x02\n" +
|
||||
"\x05group\x18\x01 \x01(\v2\x1e.hyapp.wallet.v1.ResourceGroupR\x05group\"\xbe\x02\n" +
|
||||
"\x16ListGiftConfigsRequest\x12\x1d\n" +
|
||||
"\n" +
|
||||
"request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" +
|
||||
@ -14618,7 +14635,9 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" +
|
||||
"\vactive_only\x18\a \x01(\bR\n" +
|
||||
"activeOnly\x12\x1b\n" +
|
||||
"\tregion_id\x18\b \x01(\x03R\bregionId\x12#\n" +
|
||||
"\rfilter_region\x18\t \x01(\bR\ffilterRegion\"b\n" +
|
||||
"\rfilter_region\x18\t \x01(\bR\ffilterRegion\x12$\n" +
|
||||
"\x0egift_type_code\x18\n" +
|
||||
" \x01(\tR\fgiftTypeCode\"b\n" +
|
||||
"\x17ListGiftConfigsResponse\x121\n" +
|
||||
"\x05gifts\x18\x01 \x03(\v2\x1b.hyapp.wallet.v1.GiftConfigR\x05gifts\x12\x14\n" +
|
||||
"\x05total\x18\x02 \x01(\x03R\x05total\"\x8f\x01\n" +
|
||||
|
||||
@ -32,6 +32,7 @@ message DebitGiftResponse {
|
||||
string billing_receipt_id = 1;
|
||||
string transaction_id = 2;
|
||||
int64 coin_spent = 3;
|
||||
// gift_point_added 是历史回执字段;GIFT_POINT 已下线,新送礼固定返回 0。
|
||||
int64 gift_point_added = 4;
|
||||
int64 heat_value = 5;
|
||||
string price_version = 6;
|
||||
@ -325,6 +326,7 @@ message Resource {
|
||||
bool manager_grant_enabled = 21;
|
||||
string price_type = 22;
|
||||
int64 coin_price = 23;
|
||||
// gift_point_amount 是历史字段;资源和礼物价格现在只维护真实 COIN 价格,新写入固定为 0。
|
||||
int64 gift_point_amount = 24;
|
||||
}
|
||||
|
||||
@ -369,6 +371,7 @@ message GiftConfig {
|
||||
string presentation_json = 8;
|
||||
string price_version = 9;
|
||||
int64 coin_price = 10;
|
||||
// gift_point_amount 是历史字段;送礼结算不再读取该值,新写入固定为 0。
|
||||
int64 gift_point_amount = 11;
|
||||
int64 heat_value = 12;
|
||||
int64 created_by_user_id = 13;
|
||||
@ -518,6 +521,7 @@ message CreateResourceRequest {
|
||||
optional bool manager_grant_enabled = 18;
|
||||
string price_type = 19;
|
||||
int64 coin_price = 20;
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
int64 gift_point_amount = 21;
|
||||
}
|
||||
|
||||
@ -543,6 +547,7 @@ message UpdateResourceRequest {
|
||||
optional bool manager_grant_enabled = 19;
|
||||
string price_type = 20;
|
||||
int64 coin_price = 21;
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
int64 gift_point_amount = 22;
|
||||
}
|
||||
|
||||
@ -630,6 +635,7 @@ message ListGiftConfigsRequest {
|
||||
bool active_only = 7;
|
||||
int64 region_id = 8;
|
||||
bool filter_region = 9;
|
||||
string gift_type_code = 10;
|
||||
}
|
||||
|
||||
message ListGiftConfigsResponse {
|
||||
@ -674,6 +680,7 @@ message CreateGiftConfigRequest {
|
||||
string presentation_json = 8;
|
||||
string price_version = 9;
|
||||
int64 coin_price = 10;
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
int64 gift_point_amount = 11;
|
||||
int64 heat_value = 12;
|
||||
int64 effective_at_ms = 13;
|
||||
@ -697,6 +704,7 @@ message UpdateGiftConfigRequest {
|
||||
string presentation_json = 8;
|
||||
string price_version = 9;
|
||||
int64 coin_price = 10;
|
||||
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
|
||||
int64 gift_point_amount = 11;
|
||||
int64 heat_value = 12;
|
||||
int64 effective_at_ms = 13;
|
||||
@ -967,6 +975,7 @@ message GiftWallItem {
|
||||
int64 total_value = 9;
|
||||
int64 total_coin_value = 10;
|
||||
int64 total_diamond_value = 11;
|
||||
// total_gift_point 是历史礼物墙字段;新送礼不再累加。
|
||||
int64 total_gift_point = 12;
|
||||
int64 total_heat_value = 13;
|
||||
string charge_asset_type = 14;
|
||||
@ -989,6 +998,7 @@ message GetUserGiftWallResponse {
|
||||
int64 total_value = 4;
|
||||
int64 total_coin_value = 5;
|
||||
int64 total_diamond_value = 6;
|
||||
// total_gift_point 是历史礼物墙汇总字段;新送礼不再累加。
|
||||
int64 total_gift_point = 7;
|
||||
int64 total_heat_value = 8;
|
||||
}
|
||||
|
||||
@ -386,21 +386,25 @@ type Agency struct {
|
||||
}
|
||||
|
||||
type HostProfile struct {
|
||||
UserID int64 `json:"userId,string"`
|
||||
Status string `json:"status"`
|
||||
RegionID int64 `json:"regionId"`
|
||||
CurrentAgencyID int64 `json:"currentAgencyId"`
|
||||
CurrentMembershipID int64 `json:"currentMembershipId"`
|
||||
Source string `json:"source"`
|
||||
FirstBecameHostAtMs int64 `json:"firstBecameHostAtMs"`
|
||||
CreatedAtMs int64 `json:"createdAtMs"`
|
||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||
DisplayUserID string `json:"displayUserId"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
RegionName string `json:"regionName"`
|
||||
CurrentAgencyName string `json:"currentAgencyName"`
|
||||
Diamond int64 `json:"diamond"`
|
||||
UserID int64 `json:"userId,string"`
|
||||
Status string `json:"status"`
|
||||
RegionID int64 `json:"regionId"`
|
||||
CurrentAgencyID int64 `json:"currentAgencyId"`
|
||||
CurrentMembershipID int64 `json:"currentMembershipId"`
|
||||
Source string `json:"source"`
|
||||
FirstBecameHostAtMs int64 `json:"firstBecameHostAtMs"`
|
||||
CreatedAtMs int64 `json:"createdAtMs"`
|
||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||
DisplayUserID string `json:"displayUserId"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
RegionName string `json:"regionName"`
|
||||
CurrentAgencyName string `json:"currentAgencyName"`
|
||||
Diamond int64 `json:"diamond"`
|
||||
CurrentAgencyOwnerUserID int64 `json:"currentAgencyOwnerUserId,string"`
|
||||
CurrentAgencyOwnerDisplayUserID string `json:"currentAgencyOwnerDisplayUserId"`
|
||||
CurrentAgencyOwnerUsername string `json:"currentAgencyOwnerUsername"`
|
||||
CurrentAgencyOwnerAvatar string `json:"currentAgencyOwnerAvatar"`
|
||||
}
|
||||
|
||||
type CoinSellerProfile struct {
|
||||
|
||||
@ -12,7 +12,11 @@ import (
|
||||
authcore "hyapp-admin-server/internal/service"
|
||||
)
|
||||
|
||||
const refreshCookieName = "hyapp_admin_refresh"
|
||||
const (
|
||||
refreshCookieName = "hyapp_admin_refresh"
|
||||
refreshCookiePath = "/"
|
||||
legacyRefreshCookiePath = "/api/v1/auth"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *AuthFlowService
|
||||
@ -52,7 +56,7 @@ func (h *Handler) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) Logout(c *gin.Context) {
|
||||
if token, err := c.Cookie(refreshCookieName); err == nil && token != "" {
|
||||
for _, token := range refreshCookieCandidates(c) {
|
||||
_ = h.service.Logout(token)
|
||||
}
|
||||
h.setRefreshCookie(c, "", -1)
|
||||
@ -60,28 +64,32 @@ func (h *Handler) Logout(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) Refresh(c *gin.Context) {
|
||||
token, err := c.Cookie(refreshCookieName)
|
||||
if err != nil || token == "" {
|
||||
tokens := refreshCookieCandidates(c)
|
||||
if len(tokens) == 0 {
|
||||
response.Unauthorized(c, "刷新会话不存在")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.service.Refresh(token, LoginInput{
|
||||
IP: c.ClientIP(),
|
||||
UserAgent: c.Request.UserAgent(),
|
||||
})
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "刷新会话已失效")
|
||||
return
|
||||
var result LoginResult
|
||||
var err error
|
||||
for _, token := range tokens {
|
||||
result, err = h.service.Refresh(token, LoginInput{
|
||||
IP: c.ClientIP(),
|
||||
UserAgent: c.Request.UserAgent(),
|
||||
})
|
||||
if err == nil {
|
||||
h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds()))
|
||||
response.OK(c, gin.H{
|
||||
"accessToken": result.AccessToken,
|
||||
"expiresAtMs": result.ExpiresAtMS,
|
||||
"user": shared.UserDTO(result.User),
|
||||
"permissions": result.Permissions,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds()))
|
||||
response.OK(c, gin.H{
|
||||
"accessToken": result.AccessToken,
|
||||
"expiresAtMs": result.ExpiresAtMS,
|
||||
"user": shared.UserDTO(result.User),
|
||||
"permissions": result.Permissions,
|
||||
})
|
||||
response.Unauthorized(c, "刷新会话已失效")
|
||||
}
|
||||
|
||||
func (h *Handler) Me(c *gin.Context) {
|
||||
@ -114,7 +122,24 @@ func (h *Handler) ChangePassword(c *gin.Context) {
|
||||
|
||||
func (h *Handler) setRefreshCookie(c *gin.Context, token string, maxAge int) {
|
||||
c.SetSameSite(refreshCookieSameSite(h.cfg.RefreshCookieSameSite))
|
||||
c.SetCookie(refreshCookieName, token, maxAge, "/api/v1/auth", "", h.cfg.RefreshCookieSecure, true)
|
||||
c.SetCookie(refreshCookieName, token, maxAge, refreshCookiePath, "", h.cfg.RefreshCookieSecure, true)
|
||||
c.SetCookie(refreshCookieName, "", -1, legacyRefreshCookiePath, "", h.cfg.RefreshCookieSecure, true)
|
||||
}
|
||||
|
||||
func refreshCookieCandidates(c *gin.Context) []string {
|
||||
values := make([]string, 0, 2)
|
||||
seen := map[string]struct{}{}
|
||||
for _, cookie := range c.Request.Cookies() {
|
||||
if cookie.Name != refreshCookieName || cookie.Value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[cookie.Value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[cookie.Value] = struct{}{}
|
||||
values = append(values, cookie.Value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func refreshCookieSameSite(value string) http.SameSite {
|
||||
|
||||
95
server/admin/internal/modules/auth/handler_test.go
Normal file
95
server/admin/internal/modules/auth/handler_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"hyapp-admin-server/internal/config"
|
||||
authcore "hyapp-admin-server/internal/service"
|
||||
)
|
||||
|
||||
func TestRefreshUsesRootCookieAndClearsLegacyCookiePath(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
store := newFakeAuthStore(t)
|
||||
handler := testAuthHandler(store)
|
||||
login, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"})
|
||||
if err != nil {
|
||||
t.Fatalf("login: %v", err)
|
||||
}
|
||||
router := gin.New()
|
||||
router.POST("/api/v1/auth/refresh", handler.Refresh)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil)
|
||||
req.AddCookie(&http.Cookie{Name: refreshCookieName, Value: login.RefreshToken})
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
setCookies := rec.Result().Cookies()
|
||||
if !hasCookieWithPath(setCookies, refreshCookieName, refreshCookiePath) {
|
||||
t.Fatalf("refresh must set root refresh cookie: %+v", setCookies)
|
||||
}
|
||||
if !hasExpiredCookieWithPath(setCookies, refreshCookieName, legacyRefreshCookiePath) {
|
||||
t.Fatalf("refresh must clear legacy path cookie: %+v", setCookies)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshTriesDuplicateRefreshCookiesUntilOneWorks(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
store := newFakeAuthStore(t)
|
||||
handler := testAuthHandler(store)
|
||||
first, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"})
|
||||
if err != nil {
|
||||
t.Fatalf("login: %v", err)
|
||||
}
|
||||
refreshed, err := handler.service.Refresh(first.RefreshToken, LoginInput{IP: "127.0.0.1", UserAgent: "browser"})
|
||||
if err != nil {
|
||||
t.Fatalf("prime refresh: %v", err)
|
||||
}
|
||||
router := gin.New()
|
||||
router.POST("/api/v1/auth/refresh", handler.Refresh)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil)
|
||||
req.Header.Set("Cookie", refreshCookieName+"="+first.RefreshToken+"; "+refreshCookieName+"="+refreshed.RefreshToken)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if strings.Contains(rec.Body.String(), "刷新会话已失效") {
|
||||
t.Fatalf("refresh should skip stale duplicate cookie: %s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func testAuthHandler(store *fakeAuthStore) *Handler {
|
||||
cfg := config.Config{RefreshTokenTTL: 7 * 24 * time.Hour, RefreshCookieSameSite: "lax"}
|
||||
return &Handler{
|
||||
service: NewService(store, authcore.NewAuthService("test-secret", time.Hour), cfg),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func hasCookieWithPath(cookies []*http.Cookie, name string, path string) bool {
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == name && cookie.Path == path && cookie.MaxAge >= 0 && cookie.Value != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasExpiredCookieWithPath(cookies []*http.Cookie, name string, path string) bool {
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == name && cookie.Path == path && cookie.MaxAge < 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -318,10 +318,12 @@ func parseInt64ID(c *gin.Context, name string) (int64, bool) {
|
||||
func parseListQuery(c *gin.Context) (listQuery, bool) {
|
||||
options := shared.ListOptions(c)
|
||||
query := listQuery{
|
||||
Page: options.Page,
|
||||
PageSize: options.PageSize,
|
||||
Keyword: options.Keyword,
|
||||
Status: options.Status,
|
||||
Page: options.Page,
|
||||
PageSize: options.PageSize,
|
||||
Keyword: options.Keyword,
|
||||
Status: options.Status,
|
||||
SortBy: firstNonBlank(c.Query("sortBy"), c.Query("sort_by")),
|
||||
SortDirection: firstNonBlank(c.Query("sortDirection"), c.Query("sort_direction"), c.Query("order")),
|
||||
}
|
||||
var ok bool
|
||||
if query.RegionID, ok = parseOptionalInt64Query(c, "regionId", "region_id"); !ok {
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -341,6 +342,7 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
||||
FROM host_profiles hp
|
||||
LEFT JOIN users u ON u.app_code = hp.app_code AND u.user_id = hp.user_id
|
||||
LEFT JOIN agencies a ON a.app_code = hp.app_code AND a.agency_id = hp.current_agency_id
|
||||
LEFT JOIN users agency_owner ON agency_owner.app_code = a.app_code AND agency_owner.user_id = a.owner_user_id
|
||||
LEFT JOIN regions r ON r.app_code = hp.app_code AND r.region_id = hp.region_id
|
||||
WHERE hp.app_code = ?`
|
||||
args := []any{appCode}
|
||||
@ -358,24 +360,32 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
||||
}
|
||||
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
whereSQL += " AND (CAST(hp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR a.name LIKE ? OR r.name LIKE ?)"
|
||||
args = append(args, like, like, like, like, like)
|
||||
whereSQL += " AND (CAST(hp.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR a.name LIKE ? OR agency_owner.current_display_user_id LIKE ? OR agency_owner.username LIKE ? OR r.name LIKE ?)"
|
||||
args = append(args, like, like, like, like, like, like, like)
|
||||
}
|
||||
|
||||
total, err := countRows(ctx, r.db, whereSQL, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
limitSQL := "LIMIT ? OFFSET ?"
|
||||
queryArgs := append(args, query.PageSize, offset(query.Page, query.PageSize))
|
||||
if query.SortBy == "diamond" {
|
||||
limitSQL = ""
|
||||
queryArgs = args
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
||||
SELECT hp.user_id, hp.status, hp.region_id, COALESCE(hp.current_agency_id, 0),
|
||||
COALESCE(hp.current_membership_id, 0), hp.source,
|
||||
hp.first_became_host_at_ms, hp.created_at_ms, hp.updated_at_ms,
|
||||
COALESCE(u.current_display_user_id, ''), COALESCE(u.username, ''),
|
||||
COALESCE(u.avatar, ''), COALESCE(r.name, ''), COALESCE(a.name, '')
|
||||
COALESCE(u.avatar, ''), COALESCE(r.name, ''), COALESCE(a.name, ''),
|
||||
COALESCE(a.owner_user_id, 0), COALESCE(agency_owner.current_display_user_id, ''),
|
||||
COALESCE(agency_owner.username, ''), COALESCE(agency_owner.avatar, '')
|
||||
%s
|
||||
ORDER BY hp.created_at_ms DESC, hp.user_id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
|
||||
%s
|
||||
`, whereSQL, limitSQL), queryArgs...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -400,6 +410,10 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
||||
&item.Avatar,
|
||||
&item.RegionName,
|
||||
&item.CurrentAgencyName,
|
||||
&item.CurrentAgencyOwnerUserID,
|
||||
&item.CurrentAgencyOwnerDisplayUserID,
|
||||
&item.CurrentAgencyOwnerUsername,
|
||||
&item.CurrentAgencyOwnerAvatar,
|
||||
); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -416,6 +430,10 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
|
||||
for _, item := range items {
|
||||
item.Diamond = diamonds[item.UserID]
|
||||
}
|
||||
if query.SortBy == "diamond" {
|
||||
sortHostProfilesByDiamond(items, query.SortDirection)
|
||||
items = paginateHostProfiles(items, query.Page, query.PageSize)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
@ -718,6 +736,36 @@ func (r *Reader) hostPeriodDiamonds(ctx context.Context, appCode string, userIDs
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func sortHostProfilesByDiamond(items []*userclient.HostProfile, direction string) {
|
||||
sort.SliceStable(items, func(i, j int) bool {
|
||||
if items[i].Diamond == items[j].Diamond {
|
||||
return false
|
||||
}
|
||||
if direction == "asc" {
|
||||
return items[i].Diamond < items[j].Diamond
|
||||
}
|
||||
return items[i].Diamond > items[j].Diamond
|
||||
})
|
||||
}
|
||||
|
||||
func paginateHostProfiles(items []*userclient.HostProfile, page int, pageSize int) []*userclient.HostProfile {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
if start >= len(items) {
|
||||
return []*userclient.HostProfile{}
|
||||
}
|
||||
end := start + pageSize
|
||||
if end > len(items) {
|
||||
end = len(items)
|
||||
}
|
||||
return items[start:end]
|
||||
}
|
||||
|
||||
func sqlPlaceholders(count int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
|
||||
@ -9,6 +9,8 @@ type listQuery struct {
|
||||
AgencyID int64
|
||||
ParentBDUserID int64
|
||||
ParentLeaderUserID int64
|
||||
SortBy string
|
||||
SortDirection string
|
||||
}
|
||||
|
||||
type createBDLeaderRequest struct {
|
||||
|
||||
@ -472,6 +472,16 @@ func normalizeListQuery(query listQuery) listQuery {
|
||||
}
|
||||
query.Keyword = strings.TrimSpace(query.Keyword)
|
||||
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
|
||||
query.SortBy = strings.ToLower(strings.TrimSpace(query.SortBy))
|
||||
if query.SortBy != "diamond" {
|
||||
query.SortBy = ""
|
||||
query.SortDirection = ""
|
||||
return query
|
||||
}
|
||||
query.SortDirection = strings.ToLower(strings.TrimSpace(query.SortDirection))
|
||||
if query.SortDirection != "asc" {
|
||||
query.SortDirection = "desc"
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ type resourceDTO struct {
|
||||
WalletAssetAmount int64 `json:"walletAssetAmount"`
|
||||
PriceType string `json:"priceType"`
|
||||
CoinPrice int64 `json:"coinPrice"`
|
||||
GiftPointAmount int64 `json:"giftPointAmount"`
|
||||
BadgeForm string `json:"badgeForm,omitempty"`
|
||||
BadgeKind string `json:"badgeKind,omitempty"`
|
||||
LevelTrack string `json:"levelTrack,omitempty"`
|
||||
@ -74,7 +73,6 @@ type giftDTO struct {
|
||||
GiftTypeCode string `json:"giftTypeCode"`
|
||||
ChargeAssetType string `json:"chargeAssetType"`
|
||||
CoinPrice int64 `json:"coinPrice"`
|
||||
GiftPointAmount int64 `json:"giftPointAmount"`
|
||||
EffectiveFromMS int64 `json:"effectiveFromMs"`
|
||||
EffectiveToMS int64 `json:"effectiveToMs"`
|
||||
EffectTypes []string `json:"effectTypes"`
|
||||
@ -197,7 +195,6 @@ func resourceFromProto(item *walletv1.Resource) resourceDTO {
|
||||
WalletAssetAmount: item.GetWalletAssetAmount(),
|
||||
PriceType: item.GetPriceType(),
|
||||
CoinPrice: item.GetCoinPrice(),
|
||||
GiftPointAmount: item.GetGiftPointAmount(),
|
||||
BadgeForm: badgeFormForResource(item.GetResourceType(), item.GetMetadataJson()),
|
||||
BadgeKind: badgeKindForResource(item.GetResourceType(), item.GetMetadataJson()),
|
||||
LevelTrack: badgeLevelTrackForResource(item.GetResourceType(), item.GetMetadataJson()),
|
||||
@ -298,7 +295,6 @@ func giftFromProto(gift *walletv1.GiftConfig) giftDTO {
|
||||
GiftTypeCode: gift.GetGiftTypeCode(),
|
||||
ChargeAssetType: gift.GetChargeAssetType(),
|
||||
CoinPrice: gift.GetCoinPrice(),
|
||||
GiftPointAmount: gift.GetGiftPointAmount(),
|
||||
EffectiveFromMS: gift.GetEffectiveFromMs(),
|
||||
EffectiveToMS: gift.GetEffectiveToMs(),
|
||||
EffectTypes: gift.GetEffectTypes(),
|
||||
|
||||
@ -294,14 +294,15 @@ func (h *Handler) ListGifts(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
resp, err := h.wallet.ListGiftConfigs(c.Request.Context(), &walletv1.ListGiftConfigsRequest{
|
||||
RequestId: middleware.CurrentRequestID(c),
|
||||
AppCode: appctx.FromContext(c.Request.Context()),
|
||||
Status: options.Status,
|
||||
Keyword: options.Keyword,
|
||||
Page: int32(options.Page),
|
||||
PageSize: int32(options.PageSize),
|
||||
RegionId: regionID,
|
||||
FilterRegion: filterRegion,
|
||||
RequestId: middleware.CurrentRequestID(c),
|
||||
AppCode: appctx.FromContext(c.Request.Context()),
|
||||
Status: options.Status,
|
||||
Keyword: options.Keyword,
|
||||
Page: int32(options.Page),
|
||||
PageSize: int32(options.PageSize),
|
||||
RegionId: regionID,
|
||||
FilterRegion: filterRegion,
|
||||
GiftTypeCode: giftTypeCodeQuery(c),
|
||||
})
|
||||
if err != nil {
|
||||
response.ServerError(c, "获取礼物列表失败")
|
||||
@ -719,6 +720,14 @@ func optionalInt64Query(c *gin.Context, primary string, fallback string) (int64,
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func giftTypeCodeQuery(c *gin.Context) string {
|
||||
value := strings.TrimSpace(c.Query("giftTypeCode"))
|
||||
if value == "" {
|
||||
value = strings.TrimSpace(c.Query("gift_type_code"))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func optionalInt64QueryWithPresence(c *gin.Context, primary string, fallback string) (int64, bool, bool) {
|
||||
raw := strings.TrimSpace(c.Query(primary))
|
||||
if raw == "" {
|
||||
|
||||
@ -53,7 +53,6 @@ type resourceRequest struct {
|
||||
Amount int64 `json:"amount"`
|
||||
PriceType string `json:"priceType"`
|
||||
CoinPrice int64 `json:"coinPrice"`
|
||||
GiftPointAmount int64 `json:"giftPointAmount"`
|
||||
BadgeForm string `json:"badgeForm"`
|
||||
BadgeKind string `json:"badgeKind"`
|
||||
LevelTrack string `json:"levelTrack"`
|
||||
@ -134,7 +133,6 @@ type giftRequest struct {
|
||||
GiftTypeCode string `json:"giftTypeCode"`
|
||||
ChargeAssetType string `json:"chargeAssetType"`
|
||||
CoinPrice int64 `json:"coinPrice"`
|
||||
GiftPointAmount int64 `json:"giftPointAmount"`
|
||||
HeatValue int64 `json:"heatValue"` // 兼容旧后台请求;新后台不再展示或提交热度。
|
||||
EffectiveAtMS int64 `json:"effectiveAtMs"`
|
||||
EffectiveFromMS int64 `json:"effectiveFromMs"`
|
||||
@ -370,7 +368,7 @@ func (r giftRequest) createProto(c *gin.Context) *walletv1.CreateGiftConfigReque
|
||||
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
|
||||
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
|
||||
CoinPrice: r.CoinPrice,
|
||||
GiftPointAmount: r.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
HeatValue: r.CoinPrice,
|
||||
EffectiveAtMs: r.EffectiveAtMS,
|
||||
EffectiveFromMs: r.EffectiveFromMS,
|
||||
@ -395,7 +393,7 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update
|
||||
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
|
||||
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
|
||||
CoinPrice: r.CoinPrice,
|
||||
GiftPointAmount: r.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
HeatValue: r.CoinPrice,
|
||||
EffectiveAtMs: r.EffectiveAtMS,
|
||||
EffectiveFromMs: r.EffectiveFromMS,
|
||||
@ -502,7 +500,7 @@ func resourcePricing(priceType string, coinPrice int64) (string, int64, int64) {
|
||||
if priceType == resourcePriceTypeFree {
|
||||
return resourcePriceTypeFree, 0, 0
|
||||
}
|
||||
return resourcePriceTypeCoin, coinPrice, coinPrice
|
||||
return resourcePriceTypeCoin, coinPrice, 0
|
||||
}
|
||||
|
||||
func validateResourcePricing(req resourceRequest) error {
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
const (
|
||||
roomTreasureLevelCount = 7
|
||||
|
||||
defaultEnergySource = "gift_point_added"
|
||||
defaultEnergySource = "heat_value"
|
||||
defaultOpenDelayMS = int64(30_000)
|
||||
defaultBroadcastScope = "region"
|
||||
defaultRewardStackPolicy = "allow_stack"
|
||||
@ -161,7 +161,11 @@ func normalizeConfig(config RoomTreasureConfig) (RoomTreasureConfig, error) {
|
||||
return RoomTreasureConfig{}, fmt.Errorf("%w: 配置版本不能小于 0", ErrInvalidArgument)
|
||||
}
|
||||
config.EnergySource = defaultString(strings.TrimSpace(config.EnergySource), defaultEnergySource)
|
||||
if !stringIn(config.EnergySource, "gift_point_added", "heat_value") {
|
||||
if config.EnergySource == "gift_point_added" {
|
||||
// gift_point_added 是历史配置值;GIFT_POINT 已下线,后台读取旧配置时统一切到真实礼物贡献口径。
|
||||
config.EnergySource = defaultEnergySource
|
||||
}
|
||||
if config.EnergySource != defaultEnergySource {
|
||||
return RoomTreasureConfig{}, fmt.Errorf("%w: 能量来源不支持", ErrInvalidArgument)
|
||||
}
|
||||
if config.OpenDelayMS < 0 {
|
||||
|
||||
@ -213,6 +213,7 @@ func periodWindow(period string, now time.Time) (time.Time, time.Time) {
|
||||
|
||||
func (s *Service) count(ctx context.Context, appCode string, boardType string, startMS int64, endMS int64) (int64, error) {
|
||||
subject, predicate := leaderboardDimension(boardType)
|
||||
// 排行榜价值优先使用送礼时按真实扣费比例生成的 heat_value;旧交易缺字段时只退回 coin_spent/charge_amount,不再读取 GIFT_POINT。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -220,10 +221,9 @@ func (s *Service) count(ctx context.Context, appCode string, boardType string, s
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
@ -247,6 +247,7 @@ func (s *Service) count(ctx context.Context, appCode string, boardType string, s
|
||||
|
||||
func (s *Service) listItems(ctx context.Context, appCode string, boardType string, startMS int64, endMS int64, limit int, offset int) ([]Item, error) {
|
||||
subject, predicate := leaderboardDimension(boardType)
|
||||
// 排行榜列表和值排序统一使用 heat_value 口径,避免历史 gift_point 字段重新参与运营排行。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -254,10 +255,9 @@ func (s *Service) listItems(ctx context.Context, appCode string, boardType strin
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
@ -305,6 +305,7 @@ func (s *Service) listItems(ctx context.Context, appCode string, boardType strin
|
||||
|
||||
func (s *Service) rankForUser(ctx context.Context, appCode string, boardType string, startMS int64, endMS int64, userID int64) (Item, bool, error) {
|
||||
subject, predicate := leaderboardDimension(boardType)
|
||||
// 用户名次查询与列表共用真实贡献口径;只兼容读取老交易的扣费金额,不兼容读取积分字段。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -312,10 +313,9 @@ func (s *Service) rankForUser(ctx context.Context, appCode string, boardType str
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
|
||||
@ -223,6 +223,7 @@ func userLeaderboardPeriodWindow(period string, now time.Time) (time.Time, time.
|
||||
|
||||
func countUserLeaderboard(ctx context.Context, db *sql.DB, appCode string, boardType string, startMS int64, endMS int64) (int64, error) {
|
||||
subject, predicate := userLeaderboardDimension(boardType)
|
||||
// App 排行榜价值优先使用真实送礼贡献 heat_value;旧交易只兜底扣费金额,不再读取历史 GIFT_POINT 字段。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -230,10 +231,9 @@ func countUserLeaderboard(ctx context.Context, db *sql.DB, appCode string, board
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
@ -257,6 +257,7 @@ func countUserLeaderboard(ctx context.Context, db *sql.DB, appCode string, board
|
||||
|
||||
func listUserLeaderboardItems(ctx context.Context, db *sql.DB, appCode string, boardType string, startMS int64, endMS int64, limit int, offset int) ([]userLeaderboardItem, error) {
|
||||
subject, predicate := userLeaderboardDimension(boardType)
|
||||
// 列表排序和值统计统一走 heat_value,保证 App 和后台排行榜不被历史积分字段污染。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -264,10 +265,9 @@ func listUserLeaderboardItems(ctx context.Context, db *sql.DB, appCode string, b
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
@ -315,6 +315,7 @@ func listUserLeaderboardItems(ctx context.Context, db *sql.DB, appCode string, b
|
||||
|
||||
func userLeaderboardRankForUser(ctx context.Context, db *sql.DB, appCode string, boardType string, startMS int64, endMS int64, userID int64) (userLeaderboardItem, bool, error) {
|
||||
subject, predicate := userLeaderboardDimension(boardType)
|
||||
// 当前用户名次查询只按真实贡献或扣费兜底统计,不再兼容 gift_point_added/gift_point_amount。
|
||||
query := fmt.Sprintf(`
|
||||
WITH gift_facts AS (
|
||||
SELECT
|
||||
@ -322,10 +323,9 @@ func userLeaderboardRankForUser(ctx context.Context, db *sql.DB, appCode string,
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.target_user_id')) AS SIGNED) AS target_user_id,
|
||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
|
||||
COALESCE(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.coin_spent')) AS SIGNED),
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.charge_amount')) AS SIGNED),
|
||||
0
|
||||
) AS gift_value,
|
||||
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,
|
||||
|
||||
@ -254,7 +254,7 @@ INSERT IGNORE INTO room_treasure_configs (
|
||||
'lalu',
|
||||
0,
|
||||
1,
|
||||
'gift_point_added',
|
||||
'heat_value',
|
||||
30000,
|
||||
1,
|
||||
'region',
|
||||
|
||||
@ -406,7 +406,7 @@ type SendGift struct {
|
||||
BillingReceiptID string `json:"billing_receipt_id,omitempty"`
|
||||
// CoinSpent 是 sender COIN 实际扣减值,来自 wallet-service 服务端价格。
|
||||
CoinSpent int64 `json:"coin_spent,omitempty"`
|
||||
// GiftPointAdded 是 target GIFT_POINT 实际入账值,来自 wallet-service。
|
||||
// GiftPointAdded 是历史命令字段;GIFT_POINT 已下线,新送礼不再赋值,恢复逻辑只依赖 HeatValue。
|
||||
GiftPointAdded int64 `json:"gift_point_added,omitempty"`
|
||||
// HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。
|
||||
HeatValue int64 `json:"heat_value,omitempty"`
|
||||
|
||||
@ -123,7 +123,6 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
|
||||
}
|
||||
settledCommand.BillingReceiptID = billing.GetBillingReceiptId()
|
||||
settledCommand.CoinSpent = billing.GetCoinSpent()
|
||||
settledCommand.GiftPointAdded = billing.GetGiftPointAdded()
|
||||
settledCommand.HeatValue = billing.GetHeatValue()
|
||||
settledCommand.PriceVersion = billing.GetPriceVersion()
|
||||
settledCommand.GiftTypeCode = billing.GetGiftTypeCode()
|
||||
@ -259,7 +258,6 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
|
||||
"gift_count": fmt.Sprintf("%d", cmd.GiftCount),
|
||||
"billing_receipt_id": billing.GetBillingReceiptId(),
|
||||
"coin_spent": fmt.Sprintf("%d", settledCommand.CoinSpent),
|
||||
"gift_point_added": fmt.Sprintf("%d", settledCommand.GiftPointAdded),
|
||||
"target_count": fmt.Sprintf("%d", len(cmd.TargetUserIDs)),
|
||||
"target_user_ids": giftTargetUserIDsAttribute(cmd.TargetUserIDs),
|
||||
"price_version": settledCommand.PriceVersion,
|
||||
|
||||
@ -25,12 +25,11 @@ import (
|
||||
const (
|
||||
roomTreasureLevelCount = 7
|
||||
|
||||
roomTreasureDefaultEnergySource = "gift_point_added"
|
||||
roomTreasureDefaultEnergySource = "heat_value"
|
||||
roomTreasureDefaultOpenDelayMS = int64(30_000)
|
||||
roomTreasureDefaultBroadcastScope = "region"
|
||||
roomTreasureDefaultRewardStackPolicy = "allow_stack"
|
||||
|
||||
roomTreasureEnergyGiftPoint = "gift_point_added"
|
||||
roomTreasureEnergyHeatValue = "heat_value"
|
||||
|
||||
roomTreasureBroadcastNone = "none"
|
||||
@ -127,7 +126,11 @@ func normalizeRoomTreasureConfig(config RoomTreasureConfig) (RoomTreasureConfig,
|
||||
return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure config_version is invalid")
|
||||
}
|
||||
config.EnergySource = defaultRoomTreasureString(config.EnergySource, roomTreasureDefaultEnergySource)
|
||||
if config.EnergySource != roomTreasureEnergyGiftPoint && config.EnergySource != roomTreasureEnergyHeatValue {
|
||||
if config.EnergySource == "gift_point_added" {
|
||||
// 旧配置可能还存着 gift_point_added;GIFT_POINT 已下线,运行时统一按真实送礼贡献 heat_value 计算宝箱能量。
|
||||
config.EnergySource = roomTreasureEnergyHeatValue
|
||||
}
|
||||
if config.EnergySource != roomTreasureEnergyHeatValue {
|
||||
return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure energy_source is invalid")
|
||||
}
|
||||
if config.OpenDelayMS < 0 || config.BroadcastDelayMS < 0 {
|
||||
@ -327,7 +330,7 @@ func (s *Service) applyRoomTreasureGift(now time.Time, current *state.RoomState,
|
||||
treasure := treasureStateForNow(current.Treasure, cfg, now, true)
|
||||
result := treasureGiftApplyResult{touched: current.Treasure == nil || treasure.ResetAtMS != current.Treasure.ResetAtMS}
|
||||
|
||||
addedEnergy := roomTreasureEnergy(cfg, cmd.GiftID, billing.GetGiftTypeCode(), billing.GetGiftPointAdded(), billing.GetHeatValue())
|
||||
addedEnergy := roomTreasureEnergy(cfg, cmd.GiftID, billing.GetGiftTypeCode(), billing.GetHeatValue())
|
||||
effectiveEnergy := int64(0)
|
||||
overflowEnergy := int64(0)
|
||||
|
||||
@ -483,11 +486,9 @@ func roomTreasureLevelByNumber(cfg RoomTreasureConfig, level int32) RoomTreasure
|
||||
return defaultRoomTreasureConfig(cfg.AppCode).Levels[0]
|
||||
}
|
||||
|
||||
func roomTreasureEnergy(cfg RoomTreasureConfig, giftID string, giftTypeCode string, giftPointAdded int64, heatValue int64) int64 {
|
||||
base := giftPointAdded
|
||||
if cfg.EnergySource == roomTreasureEnergyHeatValue {
|
||||
base = heatValue
|
||||
}
|
||||
func roomTreasureEnergy(cfg RoomTreasureConfig, giftID string, giftTypeCode string, heatValue int64) int64 {
|
||||
// 宝箱能量只读取 wallet-service 按真实扣费和贡献比例计算出的 heat_value,避免历史 GIFT_POINT 字段污染活动进度。
|
||||
base := heatValue
|
||||
if base < 0 {
|
||||
base = 0
|
||||
}
|
||||
|
||||
@ -291,7 +291,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
||||
'lalu',
|
||||
0,
|
||||
1,
|
||||
'gift_point_added',
|
||||
'heat_value',
|
||||
30000,
|
||||
1,
|
||||
'region',
|
||||
|
||||
@ -402,7 +402,7 @@ CREATE TABLE IF NOT EXISTS wallet_gift_prices (
|
||||
status VARCHAR(32) NOT NULL COMMENT '业务状态',
|
||||
charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型',
|
||||
coin_price BIGINT NOT NULL COMMENT '金币价格',
|
||||
gift_point_amount BIGINT NOT NULL COMMENT '礼物积分数量',
|
||||
gift_point_amount BIGINT NOT NULL COMMENT '历史字段:GIFT_POINT 已下线,新写入固定 0',
|
||||
heat_value BIGINT NOT NULL COMMENT '热度值',
|
||||
effective_at_ms BIGINT NOT NULL COMMENT '生效时间,UTC epoch ms',
|
||||
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||
@ -590,7 +590,7 @@ CREATE TABLE IF NOT EXISTS resources (
|
||||
wallet_asset_amount BIGINT NOT NULL DEFAULT 0 COMMENT '钱包资产数量',
|
||||
price_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '资源价格类型:coin/free',
|
||||
coin_price BIGINT NOT NULL DEFAULT 0 COMMENT '资源金币价格',
|
||||
gift_point_amount BIGINT NOT NULL DEFAULT 0 COMMENT '资源礼物积分',
|
||||
gift_point_amount BIGINT NOT NULL DEFAULT 0 COMMENT '历史字段:GIFT_POINT 已下线,新写入固定 0',
|
||||
usage_scope_json JSON NOT NULL COMMENT '使用范围 JSON 配置或快照',
|
||||
asset_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '资产 URL',
|
||||
preview_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '预览 URL',
|
||||
@ -635,7 +635,7 @@ DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @ddl := IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'resources' AND COLUMN_NAME = 'gift_point_amount') = 0,
|
||||
'ALTER TABLE resources ADD COLUMN gift_point_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''资源礼物积分'' AFTER coin_price',
|
||||
'ALTER TABLE resources ADD COLUMN gift_point_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''历史字段:GIFT_POINT 已下线,新写入固定 0'' AFTER coin_price',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
@ -831,7 +831,7 @@ CREATE TABLE IF NOT EXISTS user_gift_wall (
|
||||
total_value BIGINT NOT NULL DEFAULT 0 COMMENT '总值',
|
||||
total_coin_value BIGINT NOT NULL DEFAULT 0 COMMENT '总金币值',
|
||||
total_diamond_value BIGINT NOT NULL DEFAULT 0 COMMENT '总钻石值',
|
||||
total_gift_point BIGINT NOT NULL DEFAULT 0 COMMENT '总礼物积分',
|
||||
total_gift_point BIGINT NOT NULL DEFAULT 0 COMMENT '历史字段:GIFT_POINT 已下线,新送礼不再累加',
|
||||
total_heat_value BIGINT NOT NULL DEFAULT 0 COMMENT '总热度值',
|
||||
charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型',
|
||||
last_price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最后价格版本',
|
||||
|
||||
@ -7,7 +7,7 @@ const (
|
||||
AssetCoin = "COIN"
|
||||
// AssetCoinSellerCoin 是币商专用金币资产,只能通过币商转账兑换成玩家普通 COIN。
|
||||
AssetCoinSellerCoin = "COIN_SELLER_COIN"
|
||||
// AssetGiftPoint 是主播礼物积分,不能直接消费或提现。
|
||||
// AssetGiftPoint 是历史礼物积分资产,只保留历史账本查询兼容;新送礼不会再给它入账。
|
||||
AssetGiftPoint = "GIFT_POINT"
|
||||
// AssetHostPeriodDiamond 是主播工资周期钻石投影,只参与工资等级结算,不进入通用钱包余额。
|
||||
AssetHostPeriodDiamond = "HOST_PERIOD_DIAMOND"
|
||||
@ -126,7 +126,7 @@ type DebitGiftCommand struct {
|
||||
type DebitGiftTargetCommand struct {
|
||||
// CommandID 是单个目标交易的幂等键;同一批量送礼必须为每个目标派生独立值。
|
||||
CommandID string
|
||||
// TargetUserID 是本次入账 GIFT_POINT 的用户。
|
||||
// TargetUserID 是本次收礼用户;GIFT_POINT 已下线,新送礼不会再给目标用户积分入账。
|
||||
TargetUserID int64
|
||||
// TargetIsHost 只能由 gateway 注入,批量目标之间不能共享该身份快照。
|
||||
TargetIsHost bool
|
||||
@ -157,11 +157,12 @@ type Receipt struct {
|
||||
CoinSpent int64
|
||||
ChargeAssetType string
|
||||
ChargeAmount int64
|
||||
GiftPointAdded int64
|
||||
HeatValue int64
|
||||
GiftTypeCode string
|
||||
PriceVersion string
|
||||
BalanceAfter int64
|
||||
// GiftPointAdded 是历史回执字段,新送礼固定为 0;房间贡献和主播周期钻石只按真实扣费金额计算。
|
||||
GiftPointAdded int64
|
||||
HeatValue int64
|
||||
GiftTypeCode string
|
||||
PriceVersion string
|
||||
BalanceAfter int64
|
||||
// HostPeriodDiamondAdded 是本次送礼写入主播工资周期账户的钻石数;非主播恒为 0。
|
||||
HostPeriodDiamondAdded int64
|
||||
// HostPeriodCycleKey 是工资周期键,当前按 UTC 月生成,后续结算按此键定位周期账户。
|
||||
@ -447,13 +448,14 @@ type GiftWallItem struct {
|
||||
TotalValue int64
|
||||
TotalCoinValue int64
|
||||
TotalDiamondValue int64
|
||||
TotalGiftPoint int64
|
||||
TotalHeatValue int64
|
||||
ChargeAssetType string
|
||||
LastPriceVersion string
|
||||
FirstReceivedAtMS int64
|
||||
LastReceivedAtMS int64
|
||||
SortOrder int32
|
||||
// TotalGiftPoint 是历史礼物墙字段,新送礼不再累加,保留用于旧数据展示兼容。
|
||||
TotalGiftPoint int64
|
||||
TotalHeatValue int64
|
||||
ChargeAssetType string
|
||||
LastPriceVersion string
|
||||
FirstReceivedAtMS int64
|
||||
LastReceivedAtMS int64
|
||||
SortOrder int32
|
||||
}
|
||||
|
||||
// UserGiftWall 汇总礼物墙首屏需要的全量统计和按礼物类型聚合列表。
|
||||
@ -464,8 +466,9 @@ type UserGiftWall struct {
|
||||
TotalValue int64
|
||||
TotalCoinValue int64
|
||||
TotalDiamondValue int64
|
||||
TotalGiftPoint int64
|
||||
TotalHeatValue int64
|
||||
// TotalGiftPoint 是历史礼物墙汇总字段,新送礼不再累加,保留用于旧数据展示兼容。
|
||||
TotalGiftPoint int64
|
||||
TotalHeatValue int64
|
||||
}
|
||||
|
||||
// RechargeProduct 是用户充值页读取的充值档位。
|
||||
|
||||
@ -79,18 +79,19 @@ const (
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
AppCode string
|
||||
ResourceID int64
|
||||
ResourceCode string
|
||||
ResourceType string
|
||||
Name string
|
||||
Status string
|
||||
Grantable bool
|
||||
GrantStrategy string
|
||||
WalletAssetType string
|
||||
WalletAssetAmount int64
|
||||
PriceType string
|
||||
CoinPrice int64
|
||||
AppCode string
|
||||
ResourceID int64
|
||||
ResourceCode string
|
||||
ResourceType string
|
||||
Name string
|
||||
Status string
|
||||
Grantable bool
|
||||
GrantStrategy string
|
||||
WalletAssetType string
|
||||
WalletAssetAmount int64
|
||||
PriceType string
|
||||
CoinPrice int64
|
||||
// GiftPointAmount 是历史字段;GIFT_POINT 已下线,新资源和礼物配置只保留真实 COIN 价格。
|
||||
GiftPointAmount int64
|
||||
UsageScopes []string
|
||||
AssetURL string
|
||||
@ -146,18 +147,19 @@ type GiftConfig struct {
|
||||
PresentationJSON string
|
||||
PriceVersion string
|
||||
CoinPrice int64
|
||||
GiftPointAmount int64
|
||||
HeatValue int64
|
||||
GiftTypeCode string
|
||||
ChargeAssetType string
|
||||
EffectiveFromMS int64
|
||||
EffectiveToMS int64
|
||||
EffectTypes []string
|
||||
CreatedByUserID int64
|
||||
UpdatedByUserID int64
|
||||
CreatedAtMS int64
|
||||
UpdatedAtMS int64
|
||||
RegionIDs []int64
|
||||
// GiftPointAmount 是历史字段;送礼结算不再读取,新配置固定为 0。
|
||||
GiftPointAmount int64
|
||||
HeatValue int64
|
||||
GiftTypeCode string
|
||||
ChargeAssetType string
|
||||
EffectiveFromMS int64
|
||||
EffectiveToMS int64
|
||||
EffectTypes []string
|
||||
CreatedByUserID int64
|
||||
UpdatedByUserID int64
|
||||
CreatedAtMS int64
|
||||
UpdatedAtMS int64
|
||||
RegionIDs []int64
|
||||
}
|
||||
|
||||
type GiftTypeConfig struct {
|
||||
@ -249,18 +251,19 @@ type ListResourcesQuery struct {
|
||||
}
|
||||
|
||||
type ResourceCommand struct {
|
||||
AppCode string
|
||||
ResourceID int64
|
||||
ResourceCode string
|
||||
ResourceType string
|
||||
Name string
|
||||
Status string
|
||||
Grantable bool
|
||||
GrantStrategy string
|
||||
WalletAssetType string
|
||||
WalletAssetAmount int64
|
||||
PriceType string
|
||||
CoinPrice int64
|
||||
AppCode string
|
||||
ResourceID int64
|
||||
ResourceCode string
|
||||
ResourceType string
|
||||
Name string
|
||||
Status string
|
||||
Grantable bool
|
||||
GrantStrategy string
|
||||
WalletAssetType string
|
||||
WalletAssetAmount int64
|
||||
PriceType string
|
||||
CoinPrice int64
|
||||
// GiftPointAmount 是历史字段;保存资源时固定为 0,保留字段只兼容旧表结构。
|
||||
GiftPointAmount int64
|
||||
UsageScopes []string
|
||||
AssetURL string
|
||||
@ -315,6 +318,7 @@ type ListGiftConfigsQuery struct {
|
||||
AppCode string
|
||||
Status string
|
||||
Keyword string
|
||||
GiftTypeCode string
|
||||
Page int32
|
||||
PageSize int32
|
||||
ActiveOnly bool
|
||||
@ -338,16 +342,17 @@ type GiftConfigCommand struct {
|
||||
PresentationJSON string
|
||||
PriceVersion string
|
||||
CoinPrice int64
|
||||
GiftPointAmount int64
|
||||
HeatValue int64
|
||||
EffectiveAtMS int64
|
||||
GiftTypeCode string
|
||||
ChargeAssetType string
|
||||
EffectiveFromMS int64
|
||||
EffectiveToMS int64
|
||||
EffectTypes []string
|
||||
OperatorUserID int64
|
||||
RegionIDs []int64
|
||||
// GiftPointAmount 是历史字段;保存礼物价格时固定为 0,业务计算只读 CoinPrice。
|
||||
GiftPointAmount int64
|
||||
HeatValue int64
|
||||
EffectiveAtMS int64
|
||||
GiftTypeCode string
|
||||
ChargeAssetType string
|
||||
EffectiveFromMS int64
|
||||
EffectiveToMS int64
|
||||
EffectTypes []string
|
||||
OperatorUserID int64
|
||||
RegionIDs []int64
|
||||
}
|
||||
|
||||
type GiftTypeConfigCommand struct {
|
||||
|
||||
@ -34,8 +34,8 @@ func walletOutboxMessageFromRecord(record mysqlstorage.WalletOutboxRecord) walle
|
||||
}
|
||||
}
|
||||
|
||||
// TestDebitGiftUsesServerPriceAndCreditsGiftPoint 验证送礼只信服务端价格并产生双边分录。
|
||||
func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) {
|
||||
// TestDebitGiftUsesServerPriceWithoutGiftPointCredit 验证送礼只信服务端价格,且礼物积分下线后不再给收礼人入账。
|
||||
func TestDebitGiftUsesServerPriceWithoutGiftPointCredit(t *testing.T) {
|
||||
repository := mysqltest.NewRepository(t)
|
||||
repository.SetBalance(10001, 100)
|
||||
repository.SetGiftPrice("rose", "v9", 7, 3, 11)
|
||||
@ -54,7 +54,7 @@ func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) {
|
||||
t.Fatalf("DebitGift failed: %v", err)
|
||||
}
|
||||
|
||||
if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 6 || receipt.HeatValue != 14 || receipt.BalanceAfter != 86 || receipt.PriceVersion != "v9" {
|
||||
if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 0 || receipt.HeatValue != 14 || receipt.BalanceAfter != 86 || receipt.PriceVersion != "v9" {
|
||||
t.Fatalf("server price settlement mismatch: %+v", receipt)
|
||||
}
|
||||
if receipt.TransactionID == "" || receipt.BillingReceiptID == "" {
|
||||
@ -72,14 +72,14 @@ func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GetBalances target failed: %v", err)
|
||||
}
|
||||
if balanceAmount(balances, ledger.AssetGiftPoint) != 6 {
|
||||
t.Fatalf("target GIFT_POINT balance mismatch: %+v", balances)
|
||||
if balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
|
||||
t.Fatalf("target GIFT_POINT must stay zero after point removal: %+v", balances)
|
||||
}
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 2 {
|
||||
t.Fatalf("gift debit should write two entries, got %d", got)
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 1 {
|
||||
t.Fatalf("gift debit should only write sender COIN entry, got %d", got)
|
||||
}
|
||||
if got := repository.CountRows("wallet_outbox", "transaction_id = ?", receipt.TransactionID); got != 3 {
|
||||
t.Fatalf("gift debit should write balance and gift outbox events, got %d", got)
|
||||
if got := repository.CountRows("wallet_outbox", "transaction_id = ?", receipt.TransactionID); got != 2 {
|
||||
t.Fatalf("gift debit should write sender balance and gift outbox events, got %d", got)
|
||||
}
|
||||
if receipt.HostPeriodDiamondAdded != 0 || receipt.HostPeriodCycleKey != "" {
|
||||
t.Fatalf("non-host gift must not credit host period diamonds: %+v", receipt)
|
||||
@ -113,7 +113,7 @@ func TestBatchDebitGiftSettlesAllTargetsAtomically(t *testing.T) {
|
||||
t.Fatalf("BatchDebitGift failed: %v", err)
|
||||
}
|
||||
|
||||
if receipt.Aggregate.CoinSpent != 28 || receipt.Aggregate.GiftPointAdded != 12 || receipt.Aggregate.HeatValue != 28 || receipt.Aggregate.BalanceAfter != 72 || len(receipt.Targets) != 2 {
|
||||
if receipt.Aggregate.CoinSpent != 28 || receipt.Aggregate.GiftPointAdded != 0 || receipt.Aggregate.HeatValue != 28 || receipt.Aggregate.BalanceAfter != 72 || len(receipt.Targets) != 2 {
|
||||
t.Fatalf("batch aggregate mismatch: %+v", receipt)
|
||||
}
|
||||
for _, userID := range []int64{10002, 10003} {
|
||||
@ -121,8 +121,8 @@ func TestBatchDebitGiftSettlesAllTargetsAtomically(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GetBalances target %d failed: %v", userID, err)
|
||||
}
|
||||
if balanceAmount(balances, ledger.AssetGiftPoint) != 6 {
|
||||
t.Fatalf("target %d GIFT_POINT mismatch: %+v", userID, balances)
|
||||
if balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
|
||||
t.Fatalf("target %d GIFT_POINT must stay zero after point removal: %+v", userID, balances)
|
||||
}
|
||||
}
|
||||
balances, err := svc.GetBalances(context.Background(), 10001, []string{ledger.AssetCoin})
|
||||
@ -135,8 +135,8 @@ func TestBatchDebitGiftSettlesAllTargetsAtomically(t *testing.T) {
|
||||
if got := repository.CountRows("wallet_transactions", "command_id IN (?, ?)", "cmd-gift-batch:target:10002", "cmd-gift-batch:target:10003"); got != 2 {
|
||||
t.Fatalf("batch gift should write one transaction per target, got %d", got)
|
||||
}
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id IN (?, ?)", receipt.Targets[0].Receipt.TransactionID, receipt.Targets[1].Receipt.TransactionID); got != 4 {
|
||||
t.Fatalf("batch gift should write sender and target entries per target, got %d", got)
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id IN (?, ?)", receipt.Targets[0].Receipt.TransactionID, receipt.Targets[1].Receipt.TransactionID); got != 2 {
|
||||
t.Fatalf("batch gift should only write sender COIN entries per target, got %d", got)
|
||||
}
|
||||
|
||||
again, err := svc.BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{
|
||||
@ -303,7 +303,7 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t
|
||||
if err != nil {
|
||||
t.Fatalf("DebitGift %s failed: %v", tc.giftType, err)
|
||||
}
|
||||
if receipt.HeatValue != tc.wantHeat || receipt.GiftPointAdded != 100 || receipt.CoinSpent != tc.coinPrice {
|
||||
if receipt.HeatValue != tc.wantHeat || receipt.GiftPointAdded != 0 || receipt.CoinSpent != tc.coinPrice {
|
||||
t.Fatalf("%s room contribution ratio mismatch: %+v want heat %d", tc.giftType, receipt, tc.wantHeat)
|
||||
}
|
||||
}
|
||||
@ -338,7 +338,7 @@ func TestBatchDebitGiftAppliesGlobalDefaultGiftDiamondRatioToEachTargetHeatValue
|
||||
t.Fatalf("batch aggregate room contribution ratio mismatch: %+v", receipt)
|
||||
}
|
||||
for _, target := range receipt.Targets {
|
||||
if target.Receipt.HeatValue != 10 || target.Receipt.GiftPointAdded != 100 {
|
||||
if target.Receipt.HeatValue != 10 || target.Receipt.GiftPointAdded != 0 {
|
||||
t.Fatalf("target room contribution ratio mismatch: %+v", target)
|
||||
}
|
||||
}
|
||||
@ -1003,7 +1003,7 @@ func TestHostSalaryHalfMonthSettlementUsesPolicyMode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDebitGiftAllowsSelfGift 验证用户可以给自己送礼,扣 COIN 和收 GIFT_POINT 仍然分别落账。
|
||||
// TestDebitGiftAllowsSelfGift 验证用户可以给自己送礼;礼物积分下线后只扣 COIN,不再回加 GIFT_POINT。
|
||||
func TestDebitGiftAllowsSelfGift(t *testing.T) {
|
||||
repository := mysqltest.NewRepository(t)
|
||||
repository.SetBalance(10001, 100)
|
||||
@ -1023,18 +1023,18 @@ func TestDebitGiftAllowsSelfGift(t *testing.T) {
|
||||
t.Fatalf("self DebitGift failed: %v", err)
|
||||
}
|
||||
|
||||
if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 6 || receipt.BalanceAfter != 86 {
|
||||
if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 0 || receipt.BalanceAfter != 86 {
|
||||
t.Fatalf("self gift settlement mismatch: %+v", receipt)
|
||||
}
|
||||
balances, err := svc.GetBalances(context.Background(), 10001, []string{ledger.AssetCoin, ledger.AssetGiftPoint})
|
||||
if err != nil {
|
||||
t.Fatalf("GetBalances failed: %v", err)
|
||||
}
|
||||
if balanceAmount(balances, ledger.AssetCoin) != 86 || balanceAmount(balances, ledger.AssetGiftPoint) != 6 {
|
||||
if balanceAmount(balances, ledger.AssetCoin) != 86 || balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
|
||||
t.Fatalf("self gift balances mismatch: %+v", balances)
|
||||
}
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 2 {
|
||||
t.Fatalf("self gift should still write debit and credit entries, got %d", got)
|
||||
if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 1 {
|
||||
t.Fatalf("self gift should only write debit entry, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2159,7 +2159,7 @@ func TestGiftConfigMustSelectGiftResource(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("DebitGift with resource-backed gift failed: %v", err)
|
||||
}
|
||||
if receipt.CoinSpent != 10 || receipt.GiftPointAdded != 4 || receipt.HeatValue != 10 {
|
||||
if receipt.CoinSpent != 10 || receipt.GiftPointAdded != 0 || receipt.HeatValue != 10 {
|
||||
t.Fatalf("resource-backed gift settlement mismatch: %+v", receipt)
|
||||
}
|
||||
}
|
||||
@ -2244,8 +2244,8 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("create priced gift resource failed: %v", err)
|
||||
}
|
||||
if paidResource.GiftPointAmount != 77 {
|
||||
t.Fatalf("resource gift point should sync coin price: %+v", paidResource)
|
||||
if paidResource.GiftPointAmount != 0 {
|
||||
t.Fatalf("resource gift point should stay disabled: %+v", paidResource)
|
||||
}
|
||||
gift, err := svc.CreateGiftConfig(ctx, resourcedomain.GiftConfigCommand{
|
||||
GiftID: "price-sync",
|
||||
@ -2264,7 +2264,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("create synced gift config failed: %v", err)
|
||||
}
|
||||
if gift.ChargeAssetType != ledger.AssetCoin || gift.CoinPrice != 77 || gift.GiftPointAmount != 77 {
|
||||
if gift.ChargeAssetType != ledger.AssetCoin || gift.CoinPrice != 77 || gift.GiftPointAmount != 0 {
|
||||
t.Fatalf("gift price should be overridden by resource price: %+v", gift)
|
||||
}
|
||||
updatedResource, err := svc.UpdateResource(ctx, resourcedomain.ResourceCommand{
|
||||
@ -2283,7 +2283,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("update priced gift resource failed: %v", err)
|
||||
}
|
||||
if updatedResource.CoinPrice != 88 || updatedResource.GiftPointAmount != 88 {
|
||||
if updatedResource.CoinPrice != 88 || updatedResource.GiftPointAmount != 0 {
|
||||
t.Fatalf("updated resource price mismatch: %+v", updatedResource)
|
||||
}
|
||||
repository.SetBalance(54001, 100)
|
||||
@ -2298,7 +2298,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("debit gift after resource price update failed: %v", err)
|
||||
}
|
||||
if updatedReceipt.CoinSpent != 88 || updatedReceipt.GiftPointAdded != 88 {
|
||||
if updatedReceipt.CoinSpent != 88 || updatedReceipt.GiftPointAdded != 0 {
|
||||
t.Fatalf("gift price should follow updated resource price: %+v", updatedReceipt)
|
||||
}
|
||||
|
||||
|
||||
@ -173,7 +173,7 @@ func (r *Repository) Ping(ctx context.Context) error {
|
||||
return r.db.PingContext(ctx)
|
||||
}
|
||||
|
||||
// DebitGift 在一个 MySQL 事务内完成 sender 扣费、target GIFT_POINT 入账、主播周期钻石入账、分录和 outbox。
|
||||
// DebitGift 在一个 MySQL 事务内完成 sender 扣费、主播周期钻石入账、分录和 outbox;历史 GIFT_POINT 回执字段保留但不再入账。
|
||||
func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return ledger.Receipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||
@ -218,10 +218,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
giftPointAdded, err := checkedMul(price.GiftPointAmount, int64(command.GiftCount))
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
// 礼物积分已经从运营配置和收礼收益里下线;回执字段只保留 0,兼容旧客户端和历史事件解析。
|
||||
roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
@ -240,7 +237,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
// 非主播不会进入该分支,因此普通用户收礼只增加 GIFT_POINT,不产生工资周期积分。
|
||||
// 非主播不会进入该分支,因此普通用户收礼不会产生工资周期钻石。
|
||||
hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, ratio.PPM)
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
@ -257,11 +254,6 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
if sender.AvailableAmount < chargeAmount {
|
||||
return ledger.Receipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
||||
}
|
||||
target, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetGiftPoint, true, nowMs)
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
|
||||
transactionID := transactionID(command.AppCode, command.CommandID)
|
||||
metadata := giftMetadata{
|
||||
AppCode: command.AppCode,
|
||||
@ -277,7 +269,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
ChargeAssetType: price.ChargeAssetType,
|
||||
ChargeAmount: chargeAmount,
|
||||
CoinSpent: chargeAmount,
|
||||
GiftPointAdded: giftPointAdded,
|
||||
GiftPointAdded: 0,
|
||||
HeatValue: heatValue,
|
||||
BalanceAfter: sender.AvailableAmount - chargeAmount,
|
||||
BillingReceipt: billingReceiptID(command.AppCode, command.CommandID),
|
||||
@ -296,7 +288,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
RoomContributionRatioPercent: roomContributionRatio.Percent,
|
||||
RoomContributionRatioRegionID: roomContributionRatioRegionID,
|
||||
CoinPrice: price.CoinPrice,
|
||||
GiftPointAmount: price.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
HeatUnitValue: price.CoinPrice,
|
||||
}
|
||||
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeGiftDebit, requestHash, fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil {
|
||||
@ -320,26 +312,8 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
}); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
if err := r.applyAccountDelta(ctx, tx, target, giftPointAdded, 0, nowMs); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
targetAfter := target.AvailableAmount + giftPointAdded
|
||||
if err := r.insertEntry(ctx, tx, walletEntry{
|
||||
TransactionID: transactionID,
|
||||
UserID: command.TargetUserID,
|
||||
AssetType: ledger.AssetGiftPoint,
|
||||
AvailableDelta: giftPointAdded,
|
||||
FrozenDelta: 0,
|
||||
AvailableAfter: targetAfter,
|
||||
FrozenAfter: target.FrozenAmount,
|
||||
CounterpartyUserID: command.SenderUserID,
|
||||
RoomID: command.RoomID,
|
||||
CreatedAtMS: nowMs,
|
||||
}); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
if hostPeriodDiamondAdded > 0 {
|
||||
// 周期钻石与送礼扣费同事务提交;如果后续任一步失败,用户扣币、主播礼物积分和工资积分会一起回滚。
|
||||
// 周期钻石与送礼扣费同事务提交;如果后续任一步失败,用户扣币和主播工资周期钻石会一起回滚。
|
||||
hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, command.CommandID, metadata, nowMs)
|
||||
if err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
@ -350,7 +324,6 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
|
||||
|
||||
events := []walletOutboxEvent{
|
||||
balanceChangedEvent(transactionID, command.CommandID, command.SenderUserID, price.ChargeAssetType, -chargeAmount, 0, senderAfter, sender.FrozenAmount, sender.Version+1, metadata, nowMs),
|
||||
balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetGiftPoint, giftPointAdded, 0, targetAfter, target.FrozenAmount, target.Version+1, metadata, nowMs),
|
||||
giftDebitedEvent(transactionID, command.CommandID, command.SenderUserID, price.ChargeAssetType, -chargeAmount, 0, metadata, nowMs),
|
||||
}
|
||||
if hostPeriodDiamondAdded > 0 {
|
||||
@ -402,10 +375,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
if err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
giftPointAdded, err := checkedMul(price.GiftPointAmount, int64(command.GiftCount))
|
||||
if err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
// 礼物积分已经下线;批量回执仍保留字段,但每个目标都固定返回 0。
|
||||
roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
|
||||
if err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
@ -466,11 +436,6 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
return ledger.BatchGiftReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
||||
}
|
||||
|
||||
targetAccounts, err := r.lockGiftTargetAccounts(ctx, tx, command.Targets, nowMs)
|
||||
if err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
|
||||
anyHostTarget := false
|
||||
for _, target := range command.Targets {
|
||||
if target.TargetIsHost {
|
||||
@ -487,7 +452,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
}
|
||||
}
|
||||
|
||||
events := make([]walletOutboxEvent, 0, len(command.Targets)*4)
|
||||
events := make([]walletOutboxEvent, 0, len(command.Targets)*3)
|
||||
targetReceipts := make([]ledger.BatchGiftTargetReceipt, 0, len(command.Targets))
|
||||
for _, target := range command.Targets {
|
||||
hostPeriodDiamondAdded := int64(0)
|
||||
@ -521,7 +486,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
ChargeAssetType: price.ChargeAssetType,
|
||||
ChargeAmount: chargeAmount,
|
||||
CoinSpent: chargeAmount,
|
||||
GiftPointAdded: giftPointAdded,
|
||||
GiftPointAdded: 0,
|
||||
HeatValue: heatValue,
|
||||
BalanceAfter: senderAfter,
|
||||
BillingReceipt: billingReceiptID(command.AppCode, target.CommandID),
|
||||
@ -540,7 +505,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
RoomContributionRatioPercent: roomContributionRatio.Percent,
|
||||
RoomContributionRatioRegionID: roomContributionRatioRegionID,
|
||||
CoinPrice: price.CoinPrice,
|
||||
GiftPointAmount: price.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
HeatUnitValue: price.CoinPrice,
|
||||
}
|
||||
if err := r.insertTransaction(ctx, tx, transactionID, target.CommandID, bizTypeGiftDebit, debitRequestHash(debitGiftCommandFromBatchTarget(command, target)), fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil {
|
||||
@ -553,16 +518,6 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
sender.AvailableAmount = senderAfter
|
||||
sender.Version++
|
||||
|
||||
targetAccount := targetAccounts[target.TargetUserID]
|
||||
if err := r.applyAccountDelta(ctx, tx, targetAccount, giftPointAdded, 0, nowMs); err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
targetAfter := targetAccount.AvailableAmount + giftPointAdded
|
||||
events = append(events, balanceChangedEvent(transactionID, target.CommandID, target.TargetUserID, ledger.AssetGiftPoint, giftPointAdded, 0, targetAfter, targetAccount.FrozenAmount, targetAccount.Version+1, metadata, nowMs))
|
||||
targetAccount.AvailableAmount = targetAfter
|
||||
targetAccount.Version++
|
||||
targetAccounts[target.TargetUserID] = targetAccount
|
||||
|
||||
if err := r.insertEntry(ctx, tx, walletEntry{
|
||||
TransactionID: transactionID,
|
||||
UserID: command.SenderUserID,
|
||||
@ -577,20 +532,6 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
|
||||
}); err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
if err := r.insertEntry(ctx, tx, walletEntry{
|
||||
TransactionID: transactionID,
|
||||
UserID: target.TargetUserID,
|
||||
AssetType: ledger.AssetGiftPoint,
|
||||
AvailableDelta: giftPointAdded,
|
||||
FrozenDelta: 0,
|
||||
AvailableAfter: targetAfter,
|
||||
FrozenAfter: targetAccount.FrozenAmount,
|
||||
CounterpartyUserID: command.SenderUserID,
|
||||
RoomID: command.RoomID,
|
||||
CreatedAtMS: nowMs,
|
||||
}); err != nil {
|
||||
return ledger.BatchGiftReceipt{}, err
|
||||
}
|
||||
if hostPeriodDiamondAdded > 0 {
|
||||
hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, target.CommandID, metadata, nowMs)
|
||||
if err != nil {
|
||||
@ -742,7 +683,7 @@ func (r *Repository) upsertUserGiftWall(ctx context.Context, tx *sql.Tx, metadat
|
||||
chargeAmount,
|
||||
totalCoinValue,
|
||||
totalDiamondValue,
|
||||
metadata.GiftPointAdded,
|
||||
0,
|
||||
metadata.HeatValue,
|
||||
chargeAssetType,
|
||||
metadata.PriceVersion,
|
||||
@ -1675,6 +1616,7 @@ type giftPrice struct {
|
||||
PriceVersion string
|
||||
ChargeAssetType string
|
||||
CoinPrice int64
|
||||
// GiftPointAmount 是 wallet_gift_prices 的历史列;送礼结算只按 CoinPrice 和比例计算,不再读取它做收益。
|
||||
GiftPointAmount int64
|
||||
HeatValue int64
|
||||
}
|
||||
@ -1716,7 +1658,7 @@ func (r *Repository) resolveGiftPrice(ctx context.Context, tx *sql.Tx, giftID st
|
||||
}
|
||||
return giftPrice{}, err
|
||||
}
|
||||
if price.CoinPrice < 0 || price.GiftPointAmount < 0 || price.HeatValue < 0 {
|
||||
if price.CoinPrice < 0 || price.HeatValue < 0 {
|
||||
return giftPrice{}, xerr.New(xerr.Internal, "gift price is invalid")
|
||||
}
|
||||
price.ChargeAssetType = ledger.NormalizeGiftChargeAssetType(price.ChargeAssetType)
|
||||
@ -2537,22 +2479,24 @@ func (r *Repository) balanceAfterTransaction(ctx context.Context, tx *sql.Tx, tr
|
||||
}
|
||||
|
||||
type giftMetadata struct {
|
||||
AppCode string `json:"app_code"`
|
||||
GiftID string `json:"gift_id"`
|
||||
GiftName string `json:"gift_name"`
|
||||
ResourceID int64 `json:"resource_id"`
|
||||
ResourceSnapshot string `json:"resource_snapshot_json"`
|
||||
GiftTypeCode string `json:"gift_type_code"`
|
||||
PresentationJSON string `json:"presentation_json"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
GiftCount int32 `json:"gift_count"`
|
||||
PriceVersion string `json:"price_version"`
|
||||
CoinPrice int64 `json:"coin_price"`
|
||||
GiftPointAmount int64 `json:"gift_point_amount"`
|
||||
HeatUnitValue int64 `json:"heat_unit_value"`
|
||||
ChargeAssetType string `json:"charge_asset_type"`
|
||||
ChargeAmount int64 `json:"charge_amount"`
|
||||
CoinSpent int64 `json:"coin_spent"`
|
||||
AppCode string `json:"app_code"`
|
||||
GiftID string `json:"gift_id"`
|
||||
GiftName string `json:"gift_name"`
|
||||
ResourceID int64 `json:"resource_id"`
|
||||
ResourceSnapshot string `json:"resource_snapshot_json"`
|
||||
GiftTypeCode string `json:"gift_type_code"`
|
||||
PresentationJSON string `json:"presentation_json"`
|
||||
SortOrder int32 `json:"sort_order"`
|
||||
GiftCount int32 `json:"gift_count"`
|
||||
PriceVersion string `json:"price_version"`
|
||||
CoinPrice int64 `json:"coin_price"`
|
||||
// GiftPointAmount 是历史礼物积分单价字段,新送礼固定写 0,业务统计只读真实扣费和热度。
|
||||
GiftPointAmount int64 `json:"gift_point_amount"`
|
||||
HeatUnitValue int64 `json:"heat_unit_value"`
|
||||
ChargeAssetType string `json:"charge_asset_type"`
|
||||
ChargeAmount int64 `json:"charge_amount"`
|
||||
CoinSpent int64 `json:"coin_spent"`
|
||||
// GiftPointAdded 是历史收礼积分字段,新送礼固定写 0,保留 JSON 字段只为旧事件解析兼容。
|
||||
GiftPointAdded int64 `json:"gift_point_added"`
|
||||
HeatValue int64 `json:"heat_value"`
|
||||
BalanceAfter int64 `json:"balance_after"`
|
||||
@ -2711,7 +2655,7 @@ func receiptFromGiftMetadata(transactionID string, metadata giftMetadata) ledger
|
||||
CoinSpent: metadata.CoinSpent,
|
||||
ChargeAssetType: chargeAssetType,
|
||||
ChargeAmount: chargeAmount,
|
||||
GiftPointAdded: metadata.GiftPointAdded,
|
||||
GiftPointAdded: 0,
|
||||
HeatValue: metadata.HeatValue,
|
||||
GiftTypeCode: metadata.GiftTypeCode,
|
||||
PriceVersion: metadata.PriceVersion,
|
||||
@ -3207,31 +3151,6 @@ func debitGiftCommandFromBatchTarget(command ledger.BatchDebitGiftCommand, targe
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) lockGiftTargetAccounts(ctx context.Context, tx *sql.Tx, targets []ledger.DebitGiftTargetCommand, nowMs int64) (map[int64]walletAccount, error) {
|
||||
ids := make([]int64, 0, len(targets))
|
||||
seen := make(map[int64]struct{}, len(targets))
|
||||
for _, target := range targets {
|
||||
if _, exists := seen[target.TargetUserID]; exists {
|
||||
continue
|
||||
}
|
||||
seen[target.TargetUserID] = struct{}{}
|
||||
ids = append(ids, target.TargetUserID)
|
||||
}
|
||||
sort.Slice(ids, func(left, right int) bool {
|
||||
return ids[left] < ids[right]
|
||||
})
|
||||
accounts := make(map[int64]walletAccount, len(ids))
|
||||
for _, targetUserID := range ids {
|
||||
// 固定按 user_id 升序锁目标账户,降低并发批量送礼互相等待时形成死锁的概率。
|
||||
account, err := r.lockAccount(ctx, tx, targetUserID, ledger.AssetGiftPoint, true, nowMs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts[targetUserID] = account
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func aggregateGiftReceipts(targets []ledger.BatchGiftTargetReceipt) (ledger.Receipt, error) {
|
||||
aggregate := ledger.Receipt{}
|
||||
billingReceiptIDs := make([]string, 0, len(targets))
|
||||
@ -3251,9 +3170,6 @@ func aggregateGiftReceipts(targets []ledger.BatchGiftTargetReceipt) (ledger.Rece
|
||||
if aggregate.ChargeAmount, err = checkedAdd(aggregate.ChargeAmount, receipt.ChargeAmount); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
if aggregate.GiftPointAdded, err = checkedAdd(aggregate.GiftPointAdded, receipt.GiftPointAdded); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
if aggregate.HeatValue, err = checkedAdd(aggregate.HeatValue, receipt.HeatValue); err != nil {
|
||||
return ledger.Receipt{}, err
|
||||
}
|
||||
|
||||
@ -292,11 +292,11 @@ func (r *Repository) syncGiftPricesForResourceTx(ctx context.Context, tx *sql.Tx
|
||||
}
|
||||
|
||||
coinPrice := int64(0)
|
||||
giftPointAmount := int64(0)
|
||||
if priceType == resourcedomain.PriceTypeCoin {
|
||||
coinPrice = resource.CoinPrice
|
||||
giftPointAmount = resource.GiftPointAmount
|
||||
}
|
||||
// gift_point_amount 是历史字段,资源改价同步礼物价格时固定写 0,避免旧积分口径重新进入送礼链路。
|
||||
giftPointAmount := int64(0)
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
UPDATE wallet_gift_prices gp
|
||||
JOIN gift_configs gc ON gc.app_code = gp.app_code AND gc.gift_id = gp.gift_id
|
||||
@ -627,7 +627,7 @@ func (r *Repository) ListGiftConfigs(ctx context.Context, query resourcedomain.L
|
||||
item.PriceVersion = price.PriceVersion
|
||||
item.ChargeAssetType = price.ChargeAssetType
|
||||
item.CoinPrice = price.CoinPrice
|
||||
item.GiftPointAmount = price.GiftPointAmount
|
||||
item.GiftPointAmount = 0
|
||||
item.HeatValue = price.CoinPrice
|
||||
}
|
||||
items = append(items, item)
|
||||
@ -2006,7 +2006,8 @@ func (r *Repository) getGiftConfig(ctx context.Context, giftID string) (resource
|
||||
gift.PriceVersion = price.PriceVersion
|
||||
gift.ChargeAssetType = price.ChargeAssetType
|
||||
gift.CoinPrice = price.CoinPrice
|
||||
gift.GiftPointAmount = price.GiftPointAmount
|
||||
// gift_point_amount 是历史列,后台和送礼主链路只暴露真实 COIN 价格。
|
||||
gift.GiftPointAmount = 0
|
||||
// heat_value 是旧配置列,后台不再读写;对兼容调用方只回真实价格,房间贡献也按真实扣费结算。
|
||||
gift.HeatValue = price.CoinPrice
|
||||
}
|
||||
@ -2837,6 +2838,10 @@ func giftConfigWhereSQL(query resourcedomain.ListGiftConfigsQuery) (string, []an
|
||||
where += ` AND (gc.gift_id LIKE ? OR gc.name LIKE ? OR r.resource_code LIKE ?)`
|
||||
args = append(args, like, like, like)
|
||||
}
|
||||
if query.GiftTypeCode != "" {
|
||||
where += ` AND gc.gift_type_code = ?`
|
||||
args = append(args, query.GiftTypeCode)
|
||||
}
|
||||
if query.FilterRegion {
|
||||
where += ` AND EXISTS (
|
||||
SELECT 1
|
||||
@ -2908,6 +2913,7 @@ func normalizeResourceGroupListQuery(query resourcedomain.ListResourceGroupsQuer
|
||||
func normalizeGiftConfigListQuery(query resourcedomain.ListGiftConfigsQuery) resourcedomain.ListGiftConfigsQuery {
|
||||
query.Status = strings.ToLower(strings.TrimSpace(query.Status))
|
||||
query.Keyword = strings.TrimSpace(query.Keyword)
|
||||
query.GiftTypeCode = resourcedomain.NormalizeGiftTypeCode(query.GiftTypeCode)
|
||||
query.Page, query.PageSize = normalizePage(query.Page, query.PageSize)
|
||||
return query
|
||||
}
|
||||
@ -3082,7 +3088,8 @@ func normalizeResourceCommand(command resourcedomain.ResourceCommand) resourcedo
|
||||
command.CoinPrice = 0
|
||||
command.GiftPointAmount = 0
|
||||
} else if command.PriceType == resourcedomain.PriceTypeCoin {
|
||||
command.GiftPointAmount = command.CoinPrice
|
||||
// 资源价格只保留真实 COIN 价格;gift_point_amount 是历史字段,写入 0 兼容旧表结构。
|
||||
command.GiftPointAmount = 0
|
||||
}
|
||||
command.AssetURL = strings.TrimSpace(command.AssetURL)
|
||||
command.PreviewURL = strings.TrimSpace(command.PreviewURL)
|
||||
@ -3124,7 +3131,7 @@ func validateResourceCommand(command resourcedomain.ResourceCommand) error {
|
||||
if command.PriceType == resourcedomain.PriceTypeCoin && command.CoinPrice <= 0 {
|
||||
return xerr.New(xerr.InvalidArgument, "resource coin price is invalid")
|
||||
}
|
||||
if command.CoinPrice < 0 || command.GiftPointAmount < 0 || command.GiftPointAmount != command.CoinPrice {
|
||||
if command.CoinPrice < 0 {
|
||||
return xerr.New(xerr.InvalidArgument, "resource price is invalid")
|
||||
}
|
||||
return nil
|
||||
@ -3265,7 +3272,7 @@ func applyResourcePricingToGiftCommand(command resourcedomain.GiftConfigCommand,
|
||||
case resourcedomain.PriceTypeCoin:
|
||||
command.ChargeAssetType = ledger.AssetCoin
|
||||
command.CoinPrice = resource.CoinPrice
|
||||
command.GiftPointAmount = resource.GiftPointAmount
|
||||
command.GiftPointAmount = 0
|
||||
case resourcedomain.PriceTypeFree:
|
||||
command.ChargeAssetType = ledger.AssetCoin
|
||||
command.CoinPrice = 0
|
||||
@ -3289,6 +3296,8 @@ func normalizeGiftConfigCommand(command resourcedomain.GiftConfigCommand) resour
|
||||
command.ChargeAssetType = ledger.AssetCoin
|
||||
}
|
||||
command.EffectTypes = normalizeGiftEffectTypes(command.EffectTypes)
|
||||
// gift_point_amount 是历史字段,礼物配置保存时固定归零,送礼结算只读取真实 COIN 价格。
|
||||
command.GiftPointAmount = 0
|
||||
if command.EffectiveAtMS < 0 {
|
||||
command.EffectiveAtMS = 0
|
||||
}
|
||||
@ -3316,7 +3325,7 @@ func validateGiftConfigCommand(command resourcedomain.GiftConfigCommand) error {
|
||||
if !resourcedomain.ValidStatus(command.Status) {
|
||||
return xerr.New(xerr.InvalidArgument, "status is invalid")
|
||||
}
|
||||
if command.CoinPrice < 0 || command.GiftPointAmount < 0 || command.HeatValue < 0 {
|
||||
if command.CoinPrice < 0 || command.HeatValue < 0 {
|
||||
return xerr.New(xerr.InvalidArgument, "gift price is invalid")
|
||||
}
|
||||
if !resourcedomain.ValidGiftTypeCode(command.GiftTypeCode) {
|
||||
|
||||
@ -128,6 +128,7 @@ func (s *Server) ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConf
|
||||
AppCode: req.GetAppCode(),
|
||||
Status: req.GetStatus(),
|
||||
Keyword: req.GetKeyword(),
|
||||
GiftTypeCode: req.GetGiftTypeCode(),
|
||||
Page: req.GetPage(),
|
||||
PageSize: req.GetPageSize(),
|
||||
ActiveOnly: req.GetActiveOnly(),
|
||||
@ -401,7 +402,7 @@ func resourceCommandFromCreate(req *walletv1.CreateResourceRequest) resourcedoma
|
||||
WalletAssetAmount: req.GetWalletAssetAmount(),
|
||||
PriceType: req.GetPriceType(),
|
||||
CoinPrice: req.GetCoinPrice(),
|
||||
GiftPointAmount: req.GetGiftPointAmount(),
|
||||
GiftPointAmount: 0,
|
||||
UsageScopes: req.GetUsageScopes(),
|
||||
AssetURL: req.GetAssetUrl(),
|
||||
PreviewURL: req.GetPreviewUrl(),
|
||||
@ -426,7 +427,7 @@ func resourceCommandFromUpdate(req *walletv1.UpdateResourceRequest) resourcedoma
|
||||
WalletAssetAmount: req.GetWalletAssetAmount(),
|
||||
PriceType: req.GetPriceType(),
|
||||
CoinPrice: req.GetCoinPrice(),
|
||||
GiftPointAmount: req.GetGiftPointAmount(),
|
||||
GiftPointAmount: 0,
|
||||
UsageScopes: req.GetUsageScopes(),
|
||||
AssetUrl: req.GetAssetUrl(),
|
||||
PreviewUrl: req.GetPreviewUrl(),
|
||||
@ -478,7 +479,7 @@ func giftConfigCommandFromCreate(req *walletv1.CreateGiftConfigRequest) resource
|
||||
PresentationJSON: req.GetPresentationJson(),
|
||||
PriceVersion: req.GetPriceVersion(),
|
||||
CoinPrice: req.GetCoinPrice(),
|
||||
GiftPointAmount: req.GetGiftPointAmount(),
|
||||
GiftPointAmount: 0,
|
||||
HeatValue: req.GetHeatValue(),
|
||||
EffectiveAtMS: req.GetEffectiveAtMs(),
|
||||
GiftTypeCode: req.GetGiftTypeCode(),
|
||||
@ -502,7 +503,7 @@ func giftConfigCommandFromUpdate(req *walletv1.UpdateGiftConfigRequest) resource
|
||||
PresentationJSON: req.GetPresentationJson(),
|
||||
PriceVersion: req.GetPriceVersion(),
|
||||
CoinPrice: req.GetCoinPrice(),
|
||||
GiftPointAmount: req.GetGiftPointAmount(),
|
||||
GiftPointAmount: 0,
|
||||
HeatValue: req.GetHeatValue(),
|
||||
EffectiveAtMS: req.GetEffectiveAtMs(),
|
||||
GiftTypeCode: req.GetGiftTypeCode(),
|
||||
@ -567,7 +568,7 @@ func resourceToProto(resource resourcedomain.Resource) *walletv1.Resource {
|
||||
WalletAssetAmount: resource.WalletAssetAmount,
|
||||
PriceType: resource.PriceType,
|
||||
CoinPrice: resource.CoinPrice,
|
||||
GiftPointAmount: resource.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
UsageScopes: resource.UsageScopes,
|
||||
AssetUrl: resource.AssetURL,
|
||||
PreviewUrl: resource.PreviewURL,
|
||||
@ -669,7 +670,7 @@ func giftConfigToProto(gift resourcedomain.GiftConfig) *walletv1.GiftConfig {
|
||||
PriceVersion: gift.PriceVersion,
|
||||
ChargeAssetType: gift.ChargeAssetType,
|
||||
CoinPrice: gift.CoinPrice,
|
||||
GiftPointAmount: gift.GiftPointAmount,
|
||||
GiftPointAmount: 0,
|
||||
HeatValue: gift.HeatValue,
|
||||
GiftTypeCode: gift.GiftTypeCode,
|
||||
EffectiveFromMs: gift.EffectiveFromMS,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user