Update wallet resources and admin host flows

This commit is contained in:
zhx 2026-06-05 14:58:40 +08:00
parent 6de77ee02d
commit 427d335e4a
27 changed files with 537 additions and 382 deletions

View File

@ -174,6 +174,7 @@ type DebitGiftResponse struct {
BillingReceiptId string `protobuf:"bytes,1,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` 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"` 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"` CoinSpent int64 `protobuf:"varint,3,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,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"` 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"` 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"` PriceVersion string `protobuf:"bytes,6,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"`
@ -2574,6 +2575,7 @@ type Resource struct {
ManagerGrantEnabled bool `protobuf:"varint,21,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3" json:"manager_grant_enabled,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,23,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;资源和礼物价格现在只维护真实 COIN 价格,新写入固定为 0。
GiftPointAmount int64 `protobuf:"varint,24,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"` GiftPointAmount int64 `protobuf:"varint,24,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -3053,6 +3055,7 @@ type GiftConfig struct {
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;送礼结算不再读取该值,新写入固定为 0。
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,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"` 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"` CreatedByUserId int64 `protobuf:"varint,13,opt,name=created_by_user_id,json=createdByUserId,proto3" json:"created_by_user_id,omitempty"`
@ -4307,6 +4310,7 @@ type CreateResourceRequest struct {
ManagerGrantEnabled *bool `protobuf:"varint,18,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3,oneof" json:"manager_grant_enabled,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,20,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
GiftPointAmount int64 `protobuf:"varint,21,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"` GiftPointAmount int64 `protobuf:"varint,21,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -4512,6 +4516,7 @@ type UpdateResourceRequest struct {
ManagerGrantEnabled *bool `protobuf:"varint,19,opt,name=manager_grant_enabled,json=managerGrantEnabled,proto3,oneof" json:"manager_grant_enabled,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,21,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
GiftPointAmount int64 `protobuf:"varint,22,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"` GiftPointAmount int64 `protobuf:"varint,22,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -5424,6 +5429,7 @@ type ListGiftConfigsRequest struct {
ActiveOnly bool `protobuf:"varint,7,opt,name=active_only,json=activeOnly,proto3" json:"active_only,omitempty"` 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"` 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"` 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -5521,6 +5527,13 @@ func (x *ListGiftConfigsRequest) GetFilterRegion() bool {
return false return false
} }
func (x *ListGiftConfigsRequest) GetGiftTypeCode() string {
if x != nil {
return x.GiftTypeCode
}
return ""
}
type ListGiftConfigsResponse struct { type ListGiftConfigsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Gifts []*GiftConfig `protobuf:"bytes,1,rep,name=gifts,proto3" json:"gifts,omitempty"` Gifts []*GiftConfig `protobuf:"bytes,1,rep,name=gifts,proto3" json:"gifts,omitempty"`
@ -5841,6 +5854,7 @@ type CreateGiftConfigRequest struct {
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,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"` 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"` EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
@ -6037,6 +6051,7 @@ type UpdateGiftConfigRequest struct {
PresentationJson string `protobuf:"bytes,8,opt,name=presentation_json,json=presentationJson,proto3" json:"presentation_json,omitempty"` 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"` 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"` CoinPrice int64 `protobuf:"varint,10,opt,name=coin_price,json=coinPrice,proto3" json:"coin_price,omitempty"`
// gift_point_amount 是历史字段;服务端忽略请求值并固定写入 0。
GiftPointAmount int64 `protobuf:"varint,11,opt,name=gift_point_amount,json=giftPointAmount,proto3" json:"gift_point_amount,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"` 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"` EffectiveAtMs int64 `protobuf:"varint,13,opt,name=effective_at_ms,json=effectiveAtMs,proto3" json:"effective_at_ms,omitempty"`
@ -8589,6 +8604,7 @@ type GiftWallItem struct {
TotalValue int64 `protobuf:"varint,9,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"` 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"` 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"` TotalDiamondValue int64 `protobuf:"varint,11,opt,name=total_diamond_value,json=totalDiamondValue,proto3" json:"total_diamond_value,omitempty"`
// total_gift_point 是历史礼物墙字段;新送礼不再累加。
TotalGiftPoint int64 `protobuf:"varint,12,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,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"` 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"` ChargeAssetType string `protobuf:"bytes,14,opt,name=charge_asset_type,json=chargeAssetType,proto3" json:"charge_asset_type,omitempty"`
@ -8824,6 +8840,7 @@ type GetUserGiftWallResponse struct {
TotalValue int64 `protobuf:"varint,4,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"` 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"` 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"` TotalDiamondValue int64 `protobuf:"varint,6,opt,name=total_diamond_value,json=totalDiamondValue,proto3" json:"total_diamond_value,omitempty"`
// total_gift_point 是历史礼物墙汇总字段;新送礼不再累加。
TotalGiftPoint int64 `protobuf:"varint,7,opt,name=total_gift_point,json=totalGiftPoint,proto3" json:"total_gift_point,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"` TotalHeatValue int64 `protobuf:"varint,8,opt,name=total_heat_value,json=totalHeatValue,proto3" json:"total_heat_value,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@ -14606,7 +14623,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" +
"\x06status\x18\x04 \x01(\tR\x06status\x12(\n" + "\x06status\x18\x04 \x01(\tR\x06status\x12(\n" +
"\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"M\n" + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"M\n" +
"\x15ResourceGroupResponse\x124\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" + "\x16ListGiftConfigsRequest\x12\x1d\n" +
"\n" + "\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x19\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" + "\vactive_only\x18\a \x01(\bR\n" +
"activeOnly\x12\x1b\n" + "activeOnly\x12\x1b\n" +
"\tregion_id\x18\b \x01(\x03R\bregionId\x12#\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" + "\x17ListGiftConfigsResponse\x121\n" +
"\x05gifts\x18\x01 \x03(\v2\x1b.hyapp.wallet.v1.GiftConfigR\x05gifts\x12\x14\n" + "\x05gifts\x18\x01 \x03(\v2\x1b.hyapp.wallet.v1.GiftConfigR\x05gifts\x12\x14\n" +
"\x05total\x18\x02 \x01(\x03R\x05total\"\x8f\x01\n" + "\x05total\x18\x02 \x01(\x03R\x05total\"\x8f\x01\n" +

View File

@ -32,6 +32,7 @@ message DebitGiftResponse {
string billing_receipt_id = 1; string billing_receipt_id = 1;
string transaction_id = 2; string transaction_id = 2;
int64 coin_spent = 3; int64 coin_spent = 3;
// gift_point_added GIFT_POINT 线 0
int64 gift_point_added = 4; int64 gift_point_added = 4;
int64 heat_value = 5; int64 heat_value = 5;
string price_version = 6; string price_version = 6;
@ -325,6 +326,7 @@ message Resource {
bool manager_grant_enabled = 21; bool manager_grant_enabled = 21;
string price_type = 22; string price_type = 22;
int64 coin_price = 23; int64 coin_price = 23;
// gift_point_amount COIN 0
int64 gift_point_amount = 24; int64 gift_point_amount = 24;
} }
@ -369,6 +371,7 @@ message GiftConfig {
string presentation_json = 8; string presentation_json = 8;
string price_version = 9; string price_version = 9;
int64 coin_price = 10; int64 coin_price = 10;
// gift_point_amount 0
int64 gift_point_amount = 11; int64 gift_point_amount = 11;
int64 heat_value = 12; int64 heat_value = 12;
int64 created_by_user_id = 13; int64 created_by_user_id = 13;
@ -518,6 +521,7 @@ message CreateResourceRequest {
optional bool manager_grant_enabled = 18; optional bool manager_grant_enabled = 18;
string price_type = 19; string price_type = 19;
int64 coin_price = 20; int64 coin_price = 20;
// gift_point_amount 0
int64 gift_point_amount = 21; int64 gift_point_amount = 21;
} }
@ -543,6 +547,7 @@ message UpdateResourceRequest {
optional bool manager_grant_enabled = 19; optional bool manager_grant_enabled = 19;
string price_type = 20; string price_type = 20;
int64 coin_price = 21; int64 coin_price = 21;
// gift_point_amount 0
int64 gift_point_amount = 22; int64 gift_point_amount = 22;
} }
@ -630,6 +635,7 @@ message ListGiftConfigsRequest {
bool active_only = 7; bool active_only = 7;
int64 region_id = 8; int64 region_id = 8;
bool filter_region = 9; bool filter_region = 9;
string gift_type_code = 10;
} }
message ListGiftConfigsResponse { message ListGiftConfigsResponse {
@ -674,6 +680,7 @@ message CreateGiftConfigRequest {
string presentation_json = 8; string presentation_json = 8;
string price_version = 9; string price_version = 9;
int64 coin_price = 10; int64 coin_price = 10;
// gift_point_amount 0
int64 gift_point_amount = 11; int64 gift_point_amount = 11;
int64 heat_value = 12; int64 heat_value = 12;
int64 effective_at_ms = 13; int64 effective_at_ms = 13;
@ -697,6 +704,7 @@ message UpdateGiftConfigRequest {
string presentation_json = 8; string presentation_json = 8;
string price_version = 9; string price_version = 9;
int64 coin_price = 10; int64 coin_price = 10;
// gift_point_amount 0
int64 gift_point_amount = 11; int64 gift_point_amount = 11;
int64 heat_value = 12; int64 heat_value = 12;
int64 effective_at_ms = 13; int64 effective_at_ms = 13;
@ -967,6 +975,7 @@ message GiftWallItem {
int64 total_value = 9; int64 total_value = 9;
int64 total_coin_value = 10; int64 total_coin_value = 10;
int64 total_diamond_value = 11; int64 total_diamond_value = 11;
// total_gift_point
int64 total_gift_point = 12; int64 total_gift_point = 12;
int64 total_heat_value = 13; int64 total_heat_value = 13;
string charge_asset_type = 14; string charge_asset_type = 14;
@ -989,6 +998,7 @@ message GetUserGiftWallResponse {
int64 total_value = 4; int64 total_value = 4;
int64 total_coin_value = 5; int64 total_coin_value = 5;
int64 total_diamond_value = 6; int64 total_diamond_value = 6;
// total_gift_point
int64 total_gift_point = 7; int64 total_gift_point = 7;
int64 total_heat_value = 8; int64 total_heat_value = 8;
} }

View File

@ -401,6 +401,10 @@ type HostProfile struct {
RegionName string `json:"regionName"` RegionName string `json:"regionName"`
CurrentAgencyName string `json:"currentAgencyName"` CurrentAgencyName string `json:"currentAgencyName"`
Diamond int64 `json:"diamond"` Diamond int64 `json:"diamond"`
CurrentAgencyOwnerUserID int64 `json:"currentAgencyOwnerUserId,string"`
CurrentAgencyOwnerDisplayUserID string `json:"currentAgencyOwnerDisplayUserId"`
CurrentAgencyOwnerUsername string `json:"currentAgencyOwnerUsername"`
CurrentAgencyOwnerAvatar string `json:"currentAgencyOwnerAvatar"`
} }
type CoinSellerProfile struct { type CoinSellerProfile struct {

View File

@ -12,7 +12,11 @@ import (
authcore "hyapp-admin-server/internal/service" authcore "hyapp-admin-server/internal/service"
) )
const refreshCookieName = "hyapp_admin_refresh" const (
refreshCookieName = "hyapp_admin_refresh"
refreshCookiePath = "/"
legacyRefreshCookiePath = "/api/v1/auth"
)
type Handler struct { type Handler struct {
service *AuthFlowService service *AuthFlowService
@ -52,7 +56,7 @@ func (h *Handler) Login(c *gin.Context) {
} }
func (h *Handler) Logout(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.service.Logout(token)
} }
h.setRefreshCookie(c, "", -1) h.setRefreshCookie(c, "", -1)
@ -60,21 +64,20 @@ func (h *Handler) Logout(c *gin.Context) {
} }
func (h *Handler) Refresh(c *gin.Context) { func (h *Handler) Refresh(c *gin.Context) {
token, err := c.Cookie(refreshCookieName) tokens := refreshCookieCandidates(c)
if err != nil || token == "" { if len(tokens) == 0 {
response.Unauthorized(c, "刷新会话不存在") response.Unauthorized(c, "刷新会话不存在")
return return
} }
result, err := h.service.Refresh(token, LoginInput{ var result LoginResult
var err error
for _, token := range tokens {
result, err = h.service.Refresh(token, LoginInput{
IP: c.ClientIP(), IP: c.ClientIP(),
UserAgent: c.Request.UserAgent(), UserAgent: c.Request.UserAgent(),
}) })
if err != nil { if err == nil {
response.Unauthorized(c, "刷新会话已失效")
return
}
h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds())) h.setRefreshCookie(c, result.RefreshToken, int(h.cfg.RefreshTokenTTL.Seconds()))
response.OK(c, gin.H{ response.OK(c, gin.H{
"accessToken": result.AccessToken, "accessToken": result.AccessToken,
@ -82,6 +85,11 @@ func (h *Handler) Refresh(c *gin.Context) {
"user": shared.UserDTO(result.User), "user": shared.UserDTO(result.User),
"permissions": result.Permissions, "permissions": result.Permissions,
}) })
return
}
}
response.Unauthorized(c, "刷新会话已失效")
} }
func (h *Handler) Me(c *gin.Context) { 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) { func (h *Handler) setRefreshCookie(c *gin.Context, token string, maxAge int) {
c.SetSameSite(refreshCookieSameSite(h.cfg.RefreshCookieSameSite)) 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 { func refreshCookieSameSite(value string) http.SameSite {

View 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
}

View File

@ -322,6 +322,8 @@ func parseListQuery(c *gin.Context) (listQuery, bool) {
PageSize: options.PageSize, PageSize: options.PageSize,
Keyword: options.Keyword, Keyword: options.Keyword,
Status: options.Status, 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 var ok bool
if query.RegionID, ok = parseOptionalInt64Query(c, "regionId", "region_id"); !ok { if query.RegionID, ok = parseOptionalInt64Query(c, "regionId", "region_id"); !ok {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"sort"
"strings" "strings"
"time" "time"
@ -341,6 +342,7 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
FROM host_profiles hp FROM host_profiles hp
LEFT JOIN users u ON u.app_code = hp.app_code AND u.user_id = hp.user_id 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 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 LEFT JOIN regions r ON r.app_code = hp.app_code AND r.region_id = hp.region_id
WHERE hp.app_code = ?` WHERE hp.app_code = ?`
args := []any{appCode} args := []any{appCode}
@ -358,24 +360,32 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
} }
if keyword := strings.TrimSpace(query.Keyword); keyword != "" { if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
like := "%" + 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 ?)" 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) args = append(args, like, like, like, like, like, like, like)
} }
total, err := countRows(ctx, r.db, whereSQL, args...) total, err := countRows(ctx, r.db, whereSQL, args...)
if err != nil { if err != nil {
return nil, 0, err 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(` rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
SELECT hp.user_id, hp.status, hp.region_id, COALESCE(hp.current_agency_id, 0), SELECT hp.user_id, hp.status, hp.region_id, COALESCE(hp.current_agency_id, 0),
COALESCE(hp.current_membership_id, 0), hp.source, COALESCE(hp.current_membership_id, 0), hp.source,
hp.first_became_host_at_ms, hp.created_at_ms, hp.updated_at_ms, 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.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 %s
ORDER BY hp.created_at_ms DESC, hp.user_id DESC ORDER BY hp.created_at_ms DESC, hp.user_id DESC
LIMIT ? OFFSET ? %s
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...) `, whereSQL, limitSQL), queryArgs...)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -400,6 +410,10 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
&item.Avatar, &item.Avatar,
&item.RegionName, &item.RegionName,
&item.CurrentAgencyName, &item.CurrentAgencyName,
&item.CurrentAgencyOwnerUserID,
&item.CurrentAgencyOwnerDisplayUserID,
&item.CurrentAgencyOwnerUsername,
&item.CurrentAgencyOwnerAvatar,
); err != nil { ); err != nil {
return nil, 0, err return nil, 0, err
} }
@ -416,6 +430,10 @@ func (r *Reader) ListHostProfiles(ctx context.Context, query listQuery) ([]*user
for _, item := range items { for _, item := range items {
item.Diamond = diamonds[item.UserID] item.Diamond = diamonds[item.UserID]
} }
if query.SortBy == "diamond" {
sortHostProfilesByDiamond(items, query.SortDirection)
items = paginateHostProfiles(items, query.Page, query.PageSize)
}
return items, total, nil return items, total, nil
} }
@ -718,6 +736,36 @@ func (r *Reader) hostPeriodDiamonds(ctx context.Context, appCode string, userIDs
return result, rows.Err() 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 { func sqlPlaceholders(count int) string {
if count <= 0 { if count <= 0 {
return "" return ""

View File

@ -9,6 +9,8 @@ type listQuery struct {
AgencyID int64 AgencyID int64
ParentBDUserID int64 ParentBDUserID int64
ParentLeaderUserID int64 ParentLeaderUserID int64
SortBy string
SortDirection string
} }
type createBDLeaderRequest struct { type createBDLeaderRequest struct {

View File

@ -472,6 +472,16 @@ func normalizeListQuery(query listQuery) listQuery {
} }
query.Keyword = strings.TrimSpace(query.Keyword) query.Keyword = strings.TrimSpace(query.Keyword)
query.Status = strings.ToLower(strings.TrimSpace(query.Status)) 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 return query
} }

View File

@ -16,7 +16,6 @@ type resourceDTO struct {
WalletAssetAmount int64 `json:"walletAssetAmount"` WalletAssetAmount int64 `json:"walletAssetAmount"`
PriceType string `json:"priceType"` PriceType string `json:"priceType"`
CoinPrice int64 `json:"coinPrice"` CoinPrice int64 `json:"coinPrice"`
GiftPointAmount int64 `json:"giftPointAmount"`
BadgeForm string `json:"badgeForm,omitempty"` BadgeForm string `json:"badgeForm,omitempty"`
BadgeKind string `json:"badgeKind,omitempty"` BadgeKind string `json:"badgeKind,omitempty"`
LevelTrack string `json:"levelTrack,omitempty"` LevelTrack string `json:"levelTrack,omitempty"`
@ -74,7 +73,6 @@ type giftDTO struct {
GiftTypeCode string `json:"giftTypeCode"` GiftTypeCode string `json:"giftTypeCode"`
ChargeAssetType string `json:"chargeAssetType"` ChargeAssetType string `json:"chargeAssetType"`
CoinPrice int64 `json:"coinPrice"` CoinPrice int64 `json:"coinPrice"`
GiftPointAmount int64 `json:"giftPointAmount"`
EffectiveFromMS int64 `json:"effectiveFromMs"` EffectiveFromMS int64 `json:"effectiveFromMs"`
EffectiveToMS int64 `json:"effectiveToMs"` EffectiveToMS int64 `json:"effectiveToMs"`
EffectTypes []string `json:"effectTypes"` EffectTypes []string `json:"effectTypes"`
@ -197,7 +195,6 @@ func resourceFromProto(item *walletv1.Resource) resourceDTO {
WalletAssetAmount: item.GetWalletAssetAmount(), WalletAssetAmount: item.GetWalletAssetAmount(),
PriceType: item.GetPriceType(), PriceType: item.GetPriceType(),
CoinPrice: item.GetCoinPrice(), CoinPrice: item.GetCoinPrice(),
GiftPointAmount: item.GetGiftPointAmount(),
BadgeForm: badgeFormForResource(item.GetResourceType(), item.GetMetadataJson()), BadgeForm: badgeFormForResource(item.GetResourceType(), item.GetMetadataJson()),
BadgeKind: badgeKindForResource(item.GetResourceType(), item.GetMetadataJson()), BadgeKind: badgeKindForResource(item.GetResourceType(), item.GetMetadataJson()),
LevelTrack: badgeLevelTrackForResource(item.GetResourceType(), item.GetMetadataJson()), LevelTrack: badgeLevelTrackForResource(item.GetResourceType(), item.GetMetadataJson()),
@ -298,7 +295,6 @@ func giftFromProto(gift *walletv1.GiftConfig) giftDTO {
GiftTypeCode: gift.GetGiftTypeCode(), GiftTypeCode: gift.GetGiftTypeCode(),
ChargeAssetType: gift.GetChargeAssetType(), ChargeAssetType: gift.GetChargeAssetType(),
CoinPrice: gift.GetCoinPrice(), CoinPrice: gift.GetCoinPrice(),
GiftPointAmount: gift.GetGiftPointAmount(),
EffectiveFromMS: gift.GetEffectiveFromMs(), EffectiveFromMS: gift.GetEffectiveFromMs(),
EffectiveToMS: gift.GetEffectiveToMs(), EffectiveToMS: gift.GetEffectiveToMs(),
EffectTypes: gift.GetEffectTypes(), EffectTypes: gift.GetEffectTypes(),

View File

@ -302,6 +302,7 @@ func (h *Handler) ListGifts(c *gin.Context) {
PageSize: int32(options.PageSize), PageSize: int32(options.PageSize),
RegionId: regionID, RegionId: regionID,
FilterRegion: filterRegion, FilterRegion: filterRegion,
GiftTypeCode: giftTypeCodeQuery(c),
}) })
if err != nil { if err != nil {
response.ServerError(c, "获取礼物列表失败") response.ServerError(c, "获取礼物列表失败")
@ -719,6 +720,14 @@ func optionalInt64Query(c *gin.Context, primary string, fallback string) (int64,
return value, ok 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) { func optionalInt64QueryWithPresence(c *gin.Context, primary string, fallback string) (int64, bool, bool) {
raw := strings.TrimSpace(c.Query(primary)) raw := strings.TrimSpace(c.Query(primary))
if raw == "" { if raw == "" {

View File

@ -53,7 +53,6 @@ type resourceRequest struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
PriceType string `json:"priceType"` PriceType string `json:"priceType"`
CoinPrice int64 `json:"coinPrice"` CoinPrice int64 `json:"coinPrice"`
GiftPointAmount int64 `json:"giftPointAmount"`
BadgeForm string `json:"badgeForm"` BadgeForm string `json:"badgeForm"`
BadgeKind string `json:"badgeKind"` BadgeKind string `json:"badgeKind"`
LevelTrack string `json:"levelTrack"` LevelTrack string `json:"levelTrack"`
@ -134,7 +133,6 @@ type giftRequest struct {
GiftTypeCode string `json:"giftTypeCode"` GiftTypeCode string `json:"giftTypeCode"`
ChargeAssetType string `json:"chargeAssetType"` ChargeAssetType string `json:"chargeAssetType"`
CoinPrice int64 `json:"coinPrice"` CoinPrice int64 `json:"coinPrice"`
GiftPointAmount int64 `json:"giftPointAmount"`
HeatValue int64 `json:"heatValue"` // 兼容旧后台请求;新后台不再展示或提交热度。 HeatValue int64 `json:"heatValue"` // 兼容旧后台请求;新后台不再展示或提交热度。
EffectiveAtMS int64 `json:"effectiveAtMs"` EffectiveAtMS int64 `json:"effectiveAtMs"`
EffectiveFromMS int64 `json:"effectiveFromMs"` EffectiveFromMS int64 `json:"effectiveFromMs"`
@ -370,7 +368,7 @@ func (r giftRequest) createProto(c *gin.Context) *walletv1.CreateGiftConfigReque
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode), GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType), ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
CoinPrice: r.CoinPrice, CoinPrice: r.CoinPrice,
GiftPointAmount: r.GiftPointAmount, GiftPointAmount: 0,
HeatValue: r.CoinPrice, HeatValue: r.CoinPrice,
EffectiveAtMs: r.EffectiveAtMS, EffectiveAtMs: r.EffectiveAtMS,
EffectiveFromMs: r.EffectiveFromMS, EffectiveFromMs: r.EffectiveFromMS,
@ -395,7 +393,7 @@ func (r giftRequest) updateProto(c *gin.Context, giftID string) *walletv1.Update
GiftTypeCode: strings.TrimSpace(r.GiftTypeCode), GiftTypeCode: strings.TrimSpace(r.GiftTypeCode),
ChargeAssetType: strings.TrimSpace(r.ChargeAssetType), ChargeAssetType: strings.TrimSpace(r.ChargeAssetType),
CoinPrice: r.CoinPrice, CoinPrice: r.CoinPrice,
GiftPointAmount: r.GiftPointAmount, GiftPointAmount: 0,
HeatValue: r.CoinPrice, HeatValue: r.CoinPrice,
EffectiveAtMs: r.EffectiveAtMS, EffectiveAtMs: r.EffectiveAtMS,
EffectiveFromMs: r.EffectiveFromMS, EffectiveFromMs: r.EffectiveFromMS,
@ -502,7 +500,7 @@ func resourcePricing(priceType string, coinPrice int64) (string, int64, int64) {
if priceType == resourcePriceTypeFree { if priceType == resourcePriceTypeFree {
return resourcePriceTypeFree, 0, 0 return resourcePriceTypeFree, 0, 0
} }
return resourcePriceTypeCoin, coinPrice, coinPrice return resourcePriceTypeCoin, coinPrice, 0
} }
func validateResourcePricing(req resourceRequest) error { func validateResourcePricing(req resourceRequest) error {

View File

@ -13,7 +13,7 @@ import (
const ( const (
roomTreasureLevelCount = 7 roomTreasureLevelCount = 7
defaultEnergySource = "gift_point_added" defaultEnergySource = "heat_value"
defaultOpenDelayMS = int64(30_000) defaultOpenDelayMS = int64(30_000)
defaultBroadcastScope = "region" defaultBroadcastScope = "region"
defaultRewardStackPolicy = "allow_stack" defaultRewardStackPolicy = "allow_stack"
@ -161,7 +161,11 @@ func normalizeConfig(config RoomTreasureConfig) (RoomTreasureConfig, error) {
return RoomTreasureConfig{}, fmt.Errorf("%w: 配置版本不能小于 0", ErrInvalidArgument) return RoomTreasureConfig{}, fmt.Errorf("%w: 配置版本不能小于 0", ErrInvalidArgument)
} }
config.EnergySource = defaultString(strings.TrimSpace(config.EnergySource), defaultEnergySource) 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) return RoomTreasureConfig{}, fmt.Errorf("%w: 能量来源不支持", ErrInvalidArgument)
} }
if config.OpenDelayMS < 0 { if config.OpenDelayMS < 0 {

View File

@ -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) { func (s *Service) count(ctx context.Context, appCode string, boardType string, startMS int64, endMS int64) (int64, error) {
subject, predicate := leaderboardDimension(boardType) subject, predicate := leaderboardDimension(boardType)
// 排行榜价值优先使用送礼时按真实扣费比例生成的 heat_value旧交易缺字段时只退回 coin_spent/charge_amount不再读取 GIFT_POINT。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, 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) { 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) subject, predicate := leaderboardDimension(boardType)
// 排行榜列表和值排序统一使用 heat_value 口径,避免历史 gift_point 字段重新参与运营排行。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, 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) { func (s *Service) rankForUser(ctx context.Context, appCode string, boardType string, startMS int64, endMS int64, userID int64) (Item, bool, error) {
subject, predicate := leaderboardDimension(boardType) subject, predicate := leaderboardDimension(boardType)
// 用户名次查询与列表共用真实贡献口径;只兼容读取老交易的扣费金额,不兼容读取积分字段。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,

View File

@ -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) { func countUserLeaderboard(ctx context.Context, db *sql.DB, appCode string, boardType string, startMS int64, endMS int64) (int64, error) {
subject, predicate := userLeaderboardDimension(boardType) subject, predicate := userLeaderboardDimension(boardType)
// App 排行榜价值优先使用真实送礼贡献 heat_value旧交易只兜底扣费金额不再读取历史 GIFT_POINT 字段。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, 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) { 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) subject, predicate := userLeaderboardDimension(boardType)
// 列表排序和值统计统一走 heat_value保证 App 和后台排行榜不被历史积分字段污染。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, 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) { 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) subject, predicate := userLeaderboardDimension(boardType)
// 当前用户名次查询只按真实贡献或扣费兜底统计,不再兼容 gift_point_added/gift_point_amount。
query := fmt.Sprintf(` query := fmt.Sprintf(`
WITH gift_facts AS ( WITH gift_facts AS (
SELECT 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, 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(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.room_id')), '') AS room_id,
COALESCE( COALESCE(
CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_point_added')) AS SIGNED), CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.heat_value')) 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, '$.coin_spent')) 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 0
) AS gift_value, ) AS gift_value,
COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count, COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.gift_count')) AS SIGNED), 0) AS gift_count,

View File

@ -254,7 +254,7 @@ INSERT IGNORE INTO room_treasure_configs (
'lalu', 'lalu',
0, 0,
1, 1,
'gift_point_added', 'heat_value',
30000, 30000,
1, 1,
'region', 'region',

View File

@ -406,7 +406,7 @@ type SendGift struct {
BillingReceiptID string `json:"billing_receipt_id,omitempty"` BillingReceiptID string `json:"billing_receipt_id,omitempty"`
// CoinSpent 是 sender COIN 实际扣减值,来自 wallet-service 服务端价格。 // CoinSpent 是 sender COIN 实际扣减值,来自 wallet-service 服务端价格。
CoinSpent int64 `json:"coin_spent,omitempty"` CoinSpent int64 `json:"coin_spent,omitempty"`
// GiftPointAdded 是 target GIFT_POINT 实际入账值,来自 wallet-service。 // GiftPointAdded 是历史命令字段GIFT_POINT 已下线,新送礼不再赋值,恢复逻辑只依赖 HeatValue。
GiftPointAdded int64 `json:"gift_point_added,omitempty"` GiftPointAdded int64 `json:"gift_point_added,omitempty"`
// HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。 // HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。
HeatValue int64 `json:"heat_value,omitempty"` HeatValue int64 `json:"heat_value,omitempty"`

View File

@ -123,7 +123,6 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r
} }
settledCommand.BillingReceiptID = billing.GetBillingReceiptId() settledCommand.BillingReceiptID = billing.GetBillingReceiptId()
settledCommand.CoinSpent = billing.GetCoinSpent() settledCommand.CoinSpent = billing.GetCoinSpent()
settledCommand.GiftPointAdded = billing.GetGiftPointAdded()
settledCommand.HeatValue = billing.GetHeatValue() settledCommand.HeatValue = billing.GetHeatValue()
settledCommand.PriceVersion = billing.GetPriceVersion() settledCommand.PriceVersion = billing.GetPriceVersion()
settledCommand.GiftTypeCode = billing.GetGiftTypeCode() 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), "gift_count": fmt.Sprintf("%d", cmd.GiftCount),
"billing_receipt_id": billing.GetBillingReceiptId(), "billing_receipt_id": billing.GetBillingReceiptId(),
"coin_spent": fmt.Sprintf("%d", settledCommand.CoinSpent), "coin_spent": fmt.Sprintf("%d", settledCommand.CoinSpent),
"gift_point_added": fmt.Sprintf("%d", settledCommand.GiftPointAdded),
"target_count": fmt.Sprintf("%d", len(cmd.TargetUserIDs)), "target_count": fmt.Sprintf("%d", len(cmd.TargetUserIDs)),
"target_user_ids": giftTargetUserIDsAttribute(cmd.TargetUserIDs), "target_user_ids": giftTargetUserIDsAttribute(cmd.TargetUserIDs),
"price_version": settledCommand.PriceVersion, "price_version": settledCommand.PriceVersion,

View File

@ -25,12 +25,11 @@ import (
const ( const (
roomTreasureLevelCount = 7 roomTreasureLevelCount = 7
roomTreasureDefaultEnergySource = "gift_point_added" roomTreasureDefaultEnergySource = "heat_value"
roomTreasureDefaultOpenDelayMS = int64(30_000) roomTreasureDefaultOpenDelayMS = int64(30_000)
roomTreasureDefaultBroadcastScope = "region" roomTreasureDefaultBroadcastScope = "region"
roomTreasureDefaultRewardStackPolicy = "allow_stack" roomTreasureDefaultRewardStackPolicy = "allow_stack"
roomTreasureEnergyGiftPoint = "gift_point_added"
roomTreasureEnergyHeatValue = "heat_value" roomTreasureEnergyHeatValue = "heat_value"
roomTreasureBroadcastNone = "none" roomTreasureBroadcastNone = "none"
@ -127,7 +126,11 @@ func normalizeRoomTreasureConfig(config RoomTreasureConfig) (RoomTreasureConfig,
return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure config_version is invalid") return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure config_version is invalid")
} }
config.EnergySource = defaultRoomTreasureString(config.EnergySource, roomTreasureDefaultEnergySource) config.EnergySource = defaultRoomTreasureString(config.EnergySource, roomTreasureDefaultEnergySource)
if config.EnergySource != roomTreasureEnergyGiftPoint && config.EnergySource != roomTreasureEnergyHeatValue { if config.EnergySource == "gift_point_added" {
// 旧配置可能还存着 gift_point_addedGIFT_POINT 已下线,运行时统一按真实送礼贡献 heat_value 计算宝箱能量。
config.EnergySource = roomTreasureEnergyHeatValue
}
if config.EnergySource != roomTreasureEnergyHeatValue {
return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure energy_source is invalid") return RoomTreasureConfig{}, xerr.New(xerr.InvalidArgument, "room treasure energy_source is invalid")
} }
if config.OpenDelayMS < 0 || config.BroadcastDelayMS < 0 { 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) treasure := treasureStateForNow(current.Treasure, cfg, now, true)
result := treasureGiftApplyResult{touched: current.Treasure == nil || treasure.ResetAtMS != current.Treasure.ResetAtMS} 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) effectiveEnergy := int64(0)
overflowEnergy := int64(0) overflowEnergy := int64(0)
@ -483,11 +486,9 @@ func roomTreasureLevelByNumber(cfg RoomTreasureConfig, level int32) RoomTreasure
return defaultRoomTreasureConfig(cfg.AppCode).Levels[0] return defaultRoomTreasureConfig(cfg.AppCode).Levels[0]
} }
func roomTreasureEnergy(cfg RoomTreasureConfig, giftID string, giftTypeCode string, giftPointAdded int64, heatValue int64) int64 { func roomTreasureEnergy(cfg RoomTreasureConfig, giftID string, giftTypeCode string, heatValue int64) int64 {
base := giftPointAdded // 宝箱能量只读取 wallet-service 按真实扣费和贡献比例计算出的 heat_value避免历史 GIFT_POINT 字段污染活动进度。
if cfg.EnergySource == roomTreasureEnergyHeatValue { base := heatValue
base = heatValue
}
if base < 0 { if base < 0 {
base = 0 base = 0
} }

View File

@ -291,7 +291,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
'lalu', 'lalu',
0, 0,
1, 1,
'gift_point_added', 'heat_value',
30000, 30000,
1, 1,
'region', 'region',

View File

@ -402,7 +402,7 @@ CREATE TABLE IF NOT EXISTS wallet_gift_prices (
status VARCHAR(32) NOT NULL COMMENT '业务状态', status VARCHAR(32) NOT NULL COMMENT '业务状态',
charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型', charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型',
coin_price BIGINT NOT NULL 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 '热度值', heat_value BIGINT NOT NULL COMMENT '热度值',
effective_at_ms BIGINT NOT NULL COMMENT '生效时间UTC epoch ms', effective_at_ms BIGINT NOT NULL COMMENT '生效时间UTC epoch ms',
created_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 '钱包资产数量', wallet_asset_amount BIGINT NOT NULL DEFAULT 0 COMMENT '钱包资产数量',
price_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '资源价格类型coin/free', price_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '资源价格类型coin/free',
coin_price BIGINT NOT NULL DEFAULT 0 COMMENT '资源金币价格', 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 配置或快照', usage_scope_json JSON NOT NULL COMMENT '使用范围 JSON 配置或快照',
asset_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '资产 URL', asset_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '资产 URL',
preview_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( SET @ddl := IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'resources' AND COLUMN_NAME = 'gift_point_amount') = 0, (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' 'SELECT 1'
); );
PREPARE stmt FROM @ddl; 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_value BIGINT NOT NULL DEFAULT 0 COMMENT '总值',
total_coin_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_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 '总热度值', total_heat_value BIGINT NOT NULL DEFAULT 0 COMMENT '总热度值',
charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型', charge_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN' COMMENT '扣费资产类型',
last_price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最后价格版本', last_price_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最后价格版本',

View File

@ -7,7 +7,7 @@ const (
AssetCoin = "COIN" AssetCoin = "COIN"
// AssetCoinSellerCoin 是币商专用金币资产,只能通过币商转账兑换成玩家普通 COIN。 // AssetCoinSellerCoin 是币商专用金币资产,只能通过币商转账兑换成玩家普通 COIN。
AssetCoinSellerCoin = "COIN_SELLER_COIN" AssetCoinSellerCoin = "COIN_SELLER_COIN"
// AssetGiftPoint 是主播礼物积分,不能直接消费或提现 // AssetGiftPoint 是历史礼物积分资产,只保留历史账本查询兼容;新送礼不会再给它入账
AssetGiftPoint = "GIFT_POINT" AssetGiftPoint = "GIFT_POINT"
// AssetHostPeriodDiamond 是主播工资周期钻石投影,只参与工资等级结算,不进入通用钱包余额。 // AssetHostPeriodDiamond 是主播工资周期钻石投影,只参与工资等级结算,不进入通用钱包余额。
AssetHostPeriodDiamond = "HOST_PERIOD_DIAMOND" AssetHostPeriodDiamond = "HOST_PERIOD_DIAMOND"
@ -126,7 +126,7 @@ type DebitGiftCommand struct {
type DebitGiftTargetCommand struct { type DebitGiftTargetCommand struct {
// CommandID 是单个目标交易的幂等键;同一批量送礼必须为每个目标派生独立值。 // CommandID 是单个目标交易的幂等键;同一批量送礼必须为每个目标派生独立值。
CommandID string CommandID string
// TargetUserID 是本次入账 GIFT_POINT 的用户 // TargetUserID 是本次收礼用户GIFT_POINT 已下线,新送礼不会再给目标用户积分入账
TargetUserID int64 TargetUserID int64
// TargetIsHost 只能由 gateway 注入,批量目标之间不能共享该身份快照。 // TargetIsHost 只能由 gateway 注入,批量目标之间不能共享该身份快照。
TargetIsHost bool TargetIsHost bool
@ -157,6 +157,7 @@ type Receipt struct {
CoinSpent int64 CoinSpent int64
ChargeAssetType string ChargeAssetType string
ChargeAmount int64 ChargeAmount int64
// GiftPointAdded 是历史回执字段,新送礼固定为 0房间贡献和主播周期钻石只按真实扣费金额计算。
GiftPointAdded int64 GiftPointAdded int64
HeatValue int64 HeatValue int64
GiftTypeCode string GiftTypeCode string
@ -447,6 +448,7 @@ type GiftWallItem struct {
TotalValue int64 TotalValue int64
TotalCoinValue int64 TotalCoinValue int64
TotalDiamondValue int64 TotalDiamondValue int64
// TotalGiftPoint 是历史礼物墙字段,新送礼不再累加,保留用于旧数据展示兼容。
TotalGiftPoint int64 TotalGiftPoint int64
TotalHeatValue int64 TotalHeatValue int64
ChargeAssetType string ChargeAssetType string
@ -464,6 +466,7 @@ type UserGiftWall struct {
TotalValue int64 TotalValue int64
TotalCoinValue int64 TotalCoinValue int64
TotalDiamondValue int64 TotalDiamondValue int64
// TotalGiftPoint 是历史礼物墙汇总字段,新送礼不再累加,保留用于旧数据展示兼容。
TotalGiftPoint int64 TotalGiftPoint int64
TotalHeatValue int64 TotalHeatValue int64
} }

View File

@ -91,6 +91,7 @@ type Resource struct {
WalletAssetAmount int64 WalletAssetAmount int64
PriceType string PriceType string
CoinPrice int64 CoinPrice int64
// GiftPointAmount 是历史字段GIFT_POINT 已下线,新资源和礼物配置只保留真实 COIN 价格。
GiftPointAmount int64 GiftPointAmount int64
UsageScopes []string UsageScopes []string
AssetURL string AssetURL string
@ -146,6 +147,7 @@ type GiftConfig struct {
PresentationJSON string PresentationJSON string
PriceVersion string PriceVersion string
CoinPrice int64 CoinPrice int64
// GiftPointAmount 是历史字段;送礼结算不再读取,新配置固定为 0。
GiftPointAmount int64 GiftPointAmount int64
HeatValue int64 HeatValue int64
GiftTypeCode string GiftTypeCode string
@ -261,6 +263,7 @@ type ResourceCommand struct {
WalletAssetAmount int64 WalletAssetAmount int64
PriceType string PriceType string
CoinPrice int64 CoinPrice int64
// GiftPointAmount 是历史字段;保存资源时固定为 0保留字段只兼容旧表结构。
GiftPointAmount int64 GiftPointAmount int64
UsageScopes []string UsageScopes []string
AssetURL string AssetURL string
@ -315,6 +318,7 @@ type ListGiftConfigsQuery struct {
AppCode string AppCode string
Status string Status string
Keyword string Keyword string
GiftTypeCode string
Page int32 Page int32
PageSize int32 PageSize int32
ActiveOnly bool ActiveOnly bool
@ -338,6 +342,7 @@ type GiftConfigCommand struct {
PresentationJSON string PresentationJSON string
PriceVersion string PriceVersion string
CoinPrice int64 CoinPrice int64
// GiftPointAmount 是历史字段;保存礼物价格时固定为 0业务计算只读 CoinPrice。
GiftPointAmount int64 GiftPointAmount int64
HeatValue int64 HeatValue int64
EffectiveAtMS int64 EffectiveAtMS int64

View File

@ -34,8 +34,8 @@ func walletOutboxMessageFromRecord(record mysqlstorage.WalletOutboxRecord) walle
} }
} }
// TestDebitGiftUsesServerPriceAndCreditsGiftPoint 验证送礼只信服务端价格并产生双边分录 // TestDebitGiftUsesServerPriceWithoutGiftPointCredit 验证送礼只信服务端价格,且礼物积分下线后不再给收礼人入账
func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) { func TestDebitGiftUsesServerPriceWithoutGiftPointCredit(t *testing.T) {
repository := mysqltest.NewRepository(t) repository := mysqltest.NewRepository(t)
repository.SetBalance(10001, 100) repository.SetBalance(10001, 100)
repository.SetGiftPrice("rose", "v9", 7, 3, 11) repository.SetGiftPrice("rose", "v9", 7, 3, 11)
@ -54,7 +54,7 @@ func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) {
t.Fatalf("DebitGift failed: %v", err) 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) t.Fatalf("server price settlement mismatch: %+v", receipt)
} }
if receipt.TransactionID == "" || receipt.BillingReceiptID == "" { if receipt.TransactionID == "" || receipt.BillingReceiptID == "" {
@ -72,14 +72,14 @@ func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("GetBalances target failed: %v", err) t.Fatalf("GetBalances target failed: %v", err)
} }
if balanceAmount(balances, ledger.AssetGiftPoint) != 6 { if balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
t.Fatalf("target GIFT_POINT balance mismatch: %+v", balances) 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 { if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 1 {
t.Fatalf("gift debit should write two entries, got %d", got) 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 { if got := repository.CountRows("wallet_outbox", "transaction_id = ?", receipt.TransactionID); got != 2 {
t.Fatalf("gift debit should write balance and gift outbox events, got %d", got) t.Fatalf("gift debit should write sender balance and gift outbox events, got %d", got)
} }
if receipt.HostPeriodDiamondAdded != 0 || receipt.HostPeriodCycleKey != "" { if receipt.HostPeriodDiamondAdded != 0 || receipt.HostPeriodCycleKey != "" {
t.Fatalf("non-host gift must not credit host period diamonds: %+v", receipt) 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) 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) t.Fatalf("batch aggregate mismatch: %+v", receipt)
} }
for _, userID := range []int64{10002, 10003} { for _, userID := range []int64{10002, 10003} {
@ -121,8 +121,8 @@ func TestBatchDebitGiftSettlesAllTargetsAtomically(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("GetBalances target %d failed: %v", userID, err) t.Fatalf("GetBalances target %d failed: %v", userID, err)
} }
if balanceAmount(balances, ledger.AssetGiftPoint) != 6 { if balanceAmount(balances, ledger.AssetGiftPoint) != 0 {
t.Fatalf("target %d GIFT_POINT mismatch: %+v", userID, balances) 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}) 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 { 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) 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 { 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 write sender and target entries per target, got %d", got) t.Fatalf("batch gift should only write sender COIN entries per target, got %d", got)
} }
again, err := svc.BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{ again, err := svc.BatchDebitGift(context.Background(), ledger.BatchDebitGiftCommand{
@ -303,7 +303,7 @@ func TestDebitGiftAppliesGlobalDefaultGiftDiamondRatioToHeatValueByGiftType(t *t
if err != nil { if err != nil {
t.Fatalf("DebitGift %s failed: %v", tc.giftType, err) 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) 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) t.Fatalf("batch aggregate room contribution ratio mismatch: %+v", receipt)
} }
for _, target := range receipt.Targets { 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) 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) { func TestDebitGiftAllowsSelfGift(t *testing.T) {
repository := mysqltest.NewRepository(t) repository := mysqltest.NewRepository(t)
repository.SetBalance(10001, 100) repository.SetBalance(10001, 100)
@ -1023,18 +1023,18 @@ func TestDebitGiftAllowsSelfGift(t *testing.T) {
t.Fatalf("self DebitGift failed: %v", err) 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) t.Fatalf("self gift settlement mismatch: %+v", receipt)
} }
balances, err := svc.GetBalances(context.Background(), 10001, []string{ledger.AssetCoin, ledger.AssetGiftPoint}) balances, err := svc.GetBalances(context.Background(), 10001, []string{ledger.AssetCoin, ledger.AssetGiftPoint})
if err != nil { if err != nil {
t.Fatalf("GetBalances failed: %v", err) 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) t.Fatalf("self gift balances mismatch: %+v", balances)
} }
if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 2 { if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 1 {
t.Fatalf("self gift should still write debit and credit entries, got %d", got) t.Fatalf("self gift should only write debit entry, got %d", got)
} }
} }
@ -2159,7 +2159,7 @@ func TestGiftConfigMustSelectGiftResource(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("DebitGift with resource-backed gift failed: %v", err) 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) t.Fatalf("resource-backed gift settlement mismatch: %+v", receipt)
} }
} }
@ -2244,8 +2244,8 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("create priced gift resource failed: %v", err) t.Fatalf("create priced gift resource failed: %v", err)
} }
if paidResource.GiftPointAmount != 77 { if paidResource.GiftPointAmount != 0 {
t.Fatalf("resource gift point should sync coin price: %+v", paidResource) t.Fatalf("resource gift point should stay disabled: %+v", paidResource)
} }
gift, err := svc.CreateGiftConfig(ctx, resourcedomain.GiftConfigCommand{ gift, err := svc.CreateGiftConfig(ctx, resourcedomain.GiftConfigCommand{
GiftID: "price-sync", GiftID: "price-sync",
@ -2264,7 +2264,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("create synced gift config failed: %v", err) 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) t.Fatalf("gift price should be overridden by resource price: %+v", gift)
} }
updatedResource, err := svc.UpdateResource(ctx, resourcedomain.ResourceCommand{ updatedResource, err := svc.UpdateResource(ctx, resourcedomain.ResourceCommand{
@ -2283,7 +2283,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("update priced gift resource failed: %v", err) 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) t.Fatalf("updated resource price mismatch: %+v", updatedResource)
} }
repository.SetBalance(54001, 100) repository.SetBalance(54001, 100)
@ -2298,7 +2298,7 @@ func TestGiftConfigSyncsConfiguredResourcePrice(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("debit gift after resource price update failed: %v", err) 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) t.Fatalf("gift price should follow updated resource price: %+v", updatedReceipt)
} }

View File

@ -173,7 +173,7 @@ func (r *Repository) Ping(ctx context.Context) error {
return r.db.PingContext(ctx) 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) { func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) {
if r == nil || r.db == nil { if r == nil || r.db == nil {
return ledger.Receipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") 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 { if err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
} }
giftPointAdded, err := checkedMul(price.GiftPointAmount, int64(command.GiftCount)) // 礼物积分已经从运营配置和收礼收益里下线;回执字段只保留 0兼容旧客户端和历史事件解析。
if err != nil {
return ledger.Receipt{}, err
}
roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode) roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
if err != nil { if err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
@ -240,7 +237,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
if err != nil { if err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
} }
// 非主播不会进入该分支,因此普通用户收礼只增加 GIFT_POINT不产生工资周期积分 // 非主播不会进入该分支,因此普通用户收礼不会产生工资周期钻石
hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, ratio.PPM) hostPeriodDiamondAdded, err = giftDiamondAmount(chargeAmount, ratio.PPM)
if err != nil { if err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
@ -257,11 +254,6 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
if sender.AvailableAmount < chargeAmount { if sender.AvailableAmount < chargeAmount {
return ledger.Receipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance") 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) transactionID := transactionID(command.AppCode, command.CommandID)
metadata := giftMetadata{ metadata := giftMetadata{
AppCode: command.AppCode, AppCode: command.AppCode,
@ -277,7 +269,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
ChargeAssetType: price.ChargeAssetType, ChargeAssetType: price.ChargeAssetType,
ChargeAmount: chargeAmount, ChargeAmount: chargeAmount,
CoinSpent: chargeAmount, CoinSpent: chargeAmount,
GiftPointAdded: giftPointAdded, GiftPointAdded: 0,
HeatValue: heatValue, HeatValue: heatValue,
BalanceAfter: sender.AvailableAmount - chargeAmount, BalanceAfter: sender.AvailableAmount - chargeAmount,
BillingReceipt: billingReceiptID(command.AppCode, command.CommandID), BillingReceipt: billingReceiptID(command.AppCode, command.CommandID),
@ -296,7 +288,7 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
RoomContributionRatioPercent: roomContributionRatio.Percent, RoomContributionRatioPercent: roomContributionRatio.Percent,
RoomContributionRatioRegionID: roomContributionRatioRegionID, RoomContributionRatioRegionID: roomContributionRatioRegionID,
CoinPrice: price.CoinPrice, CoinPrice: price.CoinPrice,
GiftPointAmount: price.GiftPointAmount, GiftPointAmount: 0,
HeatUnitValue: price.CoinPrice, 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 { 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 { }); err != nil {
return ledger.Receipt{}, err 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 { if hostPeriodDiamondAdded > 0 {
// 周期钻石与送礼扣费同事务提交;如果后续任一步失败,用户扣币、主播礼物积分和工资积分会一起回滚。 // 周期钻石与送礼扣费同事务提交;如果后续任一步失败,用户扣币和主播工资周期钻石会一起回滚。
hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, command.CommandID, metadata, nowMs) hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, command.CommandID, metadata, nowMs)
if err != nil { if err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
@ -350,7 +324,6 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm
events := []walletOutboxEvent{ 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.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), giftDebitedEvent(transactionID, command.CommandID, command.SenderUserID, price.ChargeAssetType, -chargeAmount, 0, metadata, nowMs),
} }
if hostPeriodDiamondAdded > 0 { if hostPeriodDiamondAdded > 0 {
@ -402,10 +375,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
if err != nil { if err != nil {
return ledger.BatchGiftReceipt{}, err return ledger.BatchGiftReceipt{}, err
} }
giftPointAdded, err := checkedMul(price.GiftPointAmount, int64(command.GiftCount)) // 礼物积分已经下线;批量回执仍保留字段,但每个目标都固定返回 0。
if err != nil {
return ledger.BatchGiftReceipt{}, err
}
roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode) roomContributionRatio, roomContributionRatioRegionID, err := r.resolveGlobalGiftDiamondRatio(ctx, tx, command.AppCode, giftConfig.GiftTypeCode)
if err != nil { if err != nil {
return ledger.BatchGiftReceipt{}, err 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") 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 anyHostTarget := false
for _, target := range command.Targets { for _, target := range command.Targets {
if target.TargetIsHost { 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)) targetReceipts := make([]ledger.BatchGiftTargetReceipt, 0, len(command.Targets))
for _, target := range command.Targets { for _, target := range command.Targets {
hostPeriodDiamondAdded := int64(0) hostPeriodDiamondAdded := int64(0)
@ -521,7 +486,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
ChargeAssetType: price.ChargeAssetType, ChargeAssetType: price.ChargeAssetType,
ChargeAmount: chargeAmount, ChargeAmount: chargeAmount,
CoinSpent: chargeAmount, CoinSpent: chargeAmount,
GiftPointAdded: giftPointAdded, GiftPointAdded: 0,
HeatValue: heatValue, HeatValue: heatValue,
BalanceAfter: senderAfter, BalanceAfter: senderAfter,
BillingReceipt: billingReceiptID(command.AppCode, target.CommandID), BillingReceipt: billingReceiptID(command.AppCode, target.CommandID),
@ -540,7 +505,7 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
RoomContributionRatioPercent: roomContributionRatio.Percent, RoomContributionRatioPercent: roomContributionRatio.Percent,
RoomContributionRatioRegionID: roomContributionRatioRegionID, RoomContributionRatioRegionID: roomContributionRatioRegionID,
CoinPrice: price.CoinPrice, CoinPrice: price.CoinPrice,
GiftPointAmount: price.GiftPointAmount, GiftPointAmount: 0,
HeatUnitValue: price.CoinPrice, 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 { 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.AvailableAmount = senderAfter
sender.Version++ 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{ if err := r.insertEntry(ctx, tx, walletEntry{
TransactionID: transactionID, TransactionID: transactionID,
UserID: command.SenderUserID, UserID: command.SenderUserID,
@ -577,20 +532,6 @@ func (r *Repository) BatchDebitGift(ctx context.Context, command ledger.BatchDeb
}); err != nil { }); err != nil {
return ledger.BatchGiftReceipt{}, err 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 { if hostPeriodDiamondAdded > 0 {
hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, target.CommandID, metadata, nowMs) hostPeriodDiamondAfter, hostPeriodDiamondVersion, err := r.creditHostPeriodDiamonds(ctx, tx, transactionID, target.CommandID, metadata, nowMs)
if err != nil { if err != nil {
@ -742,7 +683,7 @@ func (r *Repository) upsertUserGiftWall(ctx context.Context, tx *sql.Tx, metadat
chargeAmount, chargeAmount,
totalCoinValue, totalCoinValue,
totalDiamondValue, totalDiamondValue,
metadata.GiftPointAdded, 0,
metadata.HeatValue, metadata.HeatValue,
chargeAssetType, chargeAssetType,
metadata.PriceVersion, metadata.PriceVersion,
@ -1675,6 +1616,7 @@ type giftPrice struct {
PriceVersion string PriceVersion string
ChargeAssetType string ChargeAssetType string
CoinPrice int64 CoinPrice int64
// GiftPointAmount 是 wallet_gift_prices 的历史列;送礼结算只按 CoinPrice 和比例计算,不再读取它做收益。
GiftPointAmount int64 GiftPointAmount int64
HeatValue int64 HeatValue int64
} }
@ -1716,7 +1658,7 @@ func (r *Repository) resolveGiftPrice(ctx context.Context, tx *sql.Tx, giftID st
} }
return giftPrice{}, err 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") return giftPrice{}, xerr.New(xerr.Internal, "gift price is invalid")
} }
price.ChargeAssetType = ledger.NormalizeGiftChargeAssetType(price.ChargeAssetType) price.ChargeAssetType = ledger.NormalizeGiftChargeAssetType(price.ChargeAssetType)
@ -2548,11 +2490,13 @@ type giftMetadata struct {
GiftCount int32 `json:"gift_count"` GiftCount int32 `json:"gift_count"`
PriceVersion string `json:"price_version"` PriceVersion string `json:"price_version"`
CoinPrice int64 `json:"coin_price"` CoinPrice int64 `json:"coin_price"`
// GiftPointAmount 是历史礼物积分单价字段,新送礼固定写 0业务统计只读真实扣费和热度。
GiftPointAmount int64 `json:"gift_point_amount"` GiftPointAmount int64 `json:"gift_point_amount"`
HeatUnitValue int64 `json:"heat_unit_value"` HeatUnitValue int64 `json:"heat_unit_value"`
ChargeAssetType string `json:"charge_asset_type"` ChargeAssetType string `json:"charge_asset_type"`
ChargeAmount int64 `json:"charge_amount"` ChargeAmount int64 `json:"charge_amount"`
CoinSpent int64 `json:"coin_spent"` CoinSpent int64 `json:"coin_spent"`
// GiftPointAdded 是历史收礼积分字段,新送礼固定写 0保留 JSON 字段只为旧事件解析兼容。
GiftPointAdded int64 `json:"gift_point_added"` GiftPointAdded int64 `json:"gift_point_added"`
HeatValue int64 `json:"heat_value"` HeatValue int64 `json:"heat_value"`
BalanceAfter int64 `json:"balance_after"` BalanceAfter int64 `json:"balance_after"`
@ -2711,7 +2655,7 @@ func receiptFromGiftMetadata(transactionID string, metadata giftMetadata) ledger
CoinSpent: metadata.CoinSpent, CoinSpent: metadata.CoinSpent,
ChargeAssetType: chargeAssetType, ChargeAssetType: chargeAssetType,
ChargeAmount: chargeAmount, ChargeAmount: chargeAmount,
GiftPointAdded: metadata.GiftPointAdded, GiftPointAdded: 0,
HeatValue: metadata.HeatValue, HeatValue: metadata.HeatValue,
GiftTypeCode: metadata.GiftTypeCode, GiftTypeCode: metadata.GiftTypeCode,
PriceVersion: metadata.PriceVersion, 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) { func aggregateGiftReceipts(targets []ledger.BatchGiftTargetReceipt) (ledger.Receipt, error) {
aggregate := ledger.Receipt{} aggregate := ledger.Receipt{}
billingReceiptIDs := make([]string, 0, len(targets)) 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 { if aggregate.ChargeAmount, err = checkedAdd(aggregate.ChargeAmount, receipt.ChargeAmount); err != nil {
return ledger.Receipt{}, err 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 { if aggregate.HeatValue, err = checkedAdd(aggregate.HeatValue, receipt.HeatValue); err != nil {
return ledger.Receipt{}, err return ledger.Receipt{}, err
} }

View File

@ -292,11 +292,11 @@ func (r *Repository) syncGiftPricesForResourceTx(ctx context.Context, tx *sql.Tx
} }
coinPrice := int64(0) coinPrice := int64(0)
giftPointAmount := int64(0)
if priceType == resourcedomain.PriceTypeCoin { if priceType == resourcedomain.PriceTypeCoin {
coinPrice = resource.CoinPrice coinPrice = resource.CoinPrice
giftPointAmount = resource.GiftPointAmount
} }
// gift_point_amount 是历史字段,资源改价同步礼物价格时固定写 0避免旧积分口径重新进入送礼链路。
giftPointAmount := int64(0)
if _, err := tx.ExecContext(ctx, ` if _, err := tx.ExecContext(ctx, `
UPDATE wallet_gift_prices gp UPDATE wallet_gift_prices gp
JOIN gift_configs gc ON gc.app_code = gp.app_code AND gc.gift_id = gp.gift_id 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.PriceVersion = price.PriceVersion
item.ChargeAssetType = price.ChargeAssetType item.ChargeAssetType = price.ChargeAssetType
item.CoinPrice = price.CoinPrice item.CoinPrice = price.CoinPrice
item.GiftPointAmount = price.GiftPointAmount item.GiftPointAmount = 0
item.HeatValue = price.CoinPrice item.HeatValue = price.CoinPrice
} }
items = append(items, item) items = append(items, item)
@ -2006,7 +2006,8 @@ func (r *Repository) getGiftConfig(ctx context.Context, giftID string) (resource
gift.PriceVersion = price.PriceVersion gift.PriceVersion = price.PriceVersion
gift.ChargeAssetType = price.ChargeAssetType gift.ChargeAssetType = price.ChargeAssetType
gift.CoinPrice = price.CoinPrice gift.CoinPrice = price.CoinPrice
gift.GiftPointAmount = price.GiftPointAmount // gift_point_amount 是历史列,后台和送礼主链路只暴露真实 COIN 价格。
gift.GiftPointAmount = 0
// heat_value 是旧配置列,后台不再读写;对兼容调用方只回真实价格,房间贡献也按真实扣费结算。 // heat_value 是旧配置列,后台不再读写;对兼容调用方只回真实价格,房间贡献也按真实扣费结算。
gift.HeatValue = price.CoinPrice 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 ?)` where += ` AND (gc.gift_id LIKE ? OR gc.name LIKE ? OR r.resource_code LIKE ?)`
args = append(args, like, like, like) args = append(args, like, like, like)
} }
if query.GiftTypeCode != "" {
where += ` AND gc.gift_type_code = ?`
args = append(args, query.GiftTypeCode)
}
if query.FilterRegion { if query.FilterRegion {
where += ` AND EXISTS ( where += ` AND EXISTS (
SELECT 1 SELECT 1
@ -2908,6 +2913,7 @@ func normalizeResourceGroupListQuery(query resourcedomain.ListResourceGroupsQuer
func normalizeGiftConfigListQuery(query resourcedomain.ListGiftConfigsQuery) resourcedomain.ListGiftConfigsQuery { func normalizeGiftConfigListQuery(query resourcedomain.ListGiftConfigsQuery) resourcedomain.ListGiftConfigsQuery {
query.Status = strings.ToLower(strings.TrimSpace(query.Status)) query.Status = strings.ToLower(strings.TrimSpace(query.Status))
query.Keyword = strings.TrimSpace(query.Keyword) query.Keyword = strings.TrimSpace(query.Keyword)
query.GiftTypeCode = resourcedomain.NormalizeGiftTypeCode(query.GiftTypeCode)
query.Page, query.PageSize = normalizePage(query.Page, query.PageSize) query.Page, query.PageSize = normalizePage(query.Page, query.PageSize)
return query return query
} }
@ -3082,7 +3088,8 @@ func normalizeResourceCommand(command resourcedomain.ResourceCommand) resourcedo
command.CoinPrice = 0 command.CoinPrice = 0
command.GiftPointAmount = 0 command.GiftPointAmount = 0
} else if command.PriceType == resourcedomain.PriceTypeCoin { } 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.AssetURL = strings.TrimSpace(command.AssetURL)
command.PreviewURL = strings.TrimSpace(command.PreviewURL) command.PreviewURL = strings.TrimSpace(command.PreviewURL)
@ -3124,7 +3131,7 @@ func validateResourceCommand(command resourcedomain.ResourceCommand) error {
if command.PriceType == resourcedomain.PriceTypeCoin && command.CoinPrice <= 0 { if command.PriceType == resourcedomain.PriceTypeCoin && command.CoinPrice <= 0 {
return xerr.New(xerr.InvalidArgument, "resource coin price is invalid") 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 xerr.New(xerr.InvalidArgument, "resource price is invalid")
} }
return nil return nil
@ -3265,7 +3272,7 @@ func applyResourcePricingToGiftCommand(command resourcedomain.GiftConfigCommand,
case resourcedomain.PriceTypeCoin: case resourcedomain.PriceTypeCoin:
command.ChargeAssetType = ledger.AssetCoin command.ChargeAssetType = ledger.AssetCoin
command.CoinPrice = resource.CoinPrice command.CoinPrice = resource.CoinPrice
command.GiftPointAmount = resource.GiftPointAmount command.GiftPointAmount = 0
case resourcedomain.PriceTypeFree: case resourcedomain.PriceTypeFree:
command.ChargeAssetType = ledger.AssetCoin command.ChargeAssetType = ledger.AssetCoin
command.CoinPrice = 0 command.CoinPrice = 0
@ -3289,6 +3296,8 @@ func normalizeGiftConfigCommand(command resourcedomain.GiftConfigCommand) resour
command.ChargeAssetType = ledger.AssetCoin command.ChargeAssetType = ledger.AssetCoin
} }
command.EffectTypes = normalizeGiftEffectTypes(command.EffectTypes) command.EffectTypes = normalizeGiftEffectTypes(command.EffectTypes)
// gift_point_amount 是历史字段,礼物配置保存时固定归零,送礼结算只读取真实 COIN 价格。
command.GiftPointAmount = 0
if command.EffectiveAtMS < 0 { if command.EffectiveAtMS < 0 {
command.EffectiveAtMS = 0 command.EffectiveAtMS = 0
} }
@ -3316,7 +3325,7 @@ func validateGiftConfigCommand(command resourcedomain.GiftConfigCommand) error {
if !resourcedomain.ValidStatus(command.Status) { if !resourcedomain.ValidStatus(command.Status) {
return xerr.New(xerr.InvalidArgument, "status is invalid") 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") return xerr.New(xerr.InvalidArgument, "gift price is invalid")
} }
if !resourcedomain.ValidGiftTypeCode(command.GiftTypeCode) { if !resourcedomain.ValidGiftTypeCode(command.GiftTypeCode) {

View File

@ -128,6 +128,7 @@ func (s *Server) ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConf
AppCode: req.GetAppCode(), AppCode: req.GetAppCode(),
Status: req.GetStatus(), Status: req.GetStatus(),
Keyword: req.GetKeyword(), Keyword: req.GetKeyword(),
GiftTypeCode: req.GetGiftTypeCode(),
Page: req.GetPage(), Page: req.GetPage(),
PageSize: req.GetPageSize(), PageSize: req.GetPageSize(),
ActiveOnly: req.GetActiveOnly(), ActiveOnly: req.GetActiveOnly(),
@ -401,7 +402,7 @@ func resourceCommandFromCreate(req *walletv1.CreateResourceRequest) resourcedoma
WalletAssetAmount: req.GetWalletAssetAmount(), WalletAssetAmount: req.GetWalletAssetAmount(),
PriceType: req.GetPriceType(), PriceType: req.GetPriceType(),
CoinPrice: req.GetCoinPrice(), CoinPrice: req.GetCoinPrice(),
GiftPointAmount: req.GetGiftPointAmount(), GiftPointAmount: 0,
UsageScopes: req.GetUsageScopes(), UsageScopes: req.GetUsageScopes(),
AssetURL: req.GetAssetUrl(), AssetURL: req.GetAssetUrl(),
PreviewURL: req.GetPreviewUrl(), PreviewURL: req.GetPreviewUrl(),
@ -426,7 +427,7 @@ func resourceCommandFromUpdate(req *walletv1.UpdateResourceRequest) resourcedoma
WalletAssetAmount: req.GetWalletAssetAmount(), WalletAssetAmount: req.GetWalletAssetAmount(),
PriceType: req.GetPriceType(), PriceType: req.GetPriceType(),
CoinPrice: req.GetCoinPrice(), CoinPrice: req.GetCoinPrice(),
GiftPointAmount: req.GetGiftPointAmount(), GiftPointAmount: 0,
UsageScopes: req.GetUsageScopes(), UsageScopes: req.GetUsageScopes(),
AssetUrl: req.GetAssetUrl(), AssetUrl: req.GetAssetUrl(),
PreviewUrl: req.GetPreviewUrl(), PreviewUrl: req.GetPreviewUrl(),
@ -478,7 +479,7 @@ func giftConfigCommandFromCreate(req *walletv1.CreateGiftConfigRequest) resource
PresentationJSON: req.GetPresentationJson(), PresentationJSON: req.GetPresentationJson(),
PriceVersion: req.GetPriceVersion(), PriceVersion: req.GetPriceVersion(),
CoinPrice: req.GetCoinPrice(), CoinPrice: req.GetCoinPrice(),
GiftPointAmount: req.GetGiftPointAmount(), GiftPointAmount: 0,
HeatValue: req.GetHeatValue(), HeatValue: req.GetHeatValue(),
EffectiveAtMS: req.GetEffectiveAtMs(), EffectiveAtMS: req.GetEffectiveAtMs(),
GiftTypeCode: req.GetGiftTypeCode(), GiftTypeCode: req.GetGiftTypeCode(),
@ -502,7 +503,7 @@ func giftConfigCommandFromUpdate(req *walletv1.UpdateGiftConfigRequest) resource
PresentationJSON: req.GetPresentationJson(), PresentationJSON: req.GetPresentationJson(),
PriceVersion: req.GetPriceVersion(), PriceVersion: req.GetPriceVersion(),
CoinPrice: req.GetCoinPrice(), CoinPrice: req.GetCoinPrice(),
GiftPointAmount: req.GetGiftPointAmount(), GiftPointAmount: 0,
HeatValue: req.GetHeatValue(), HeatValue: req.GetHeatValue(),
EffectiveAtMS: req.GetEffectiveAtMs(), EffectiveAtMS: req.GetEffectiveAtMs(),
GiftTypeCode: req.GetGiftTypeCode(), GiftTypeCode: req.GetGiftTypeCode(),
@ -567,7 +568,7 @@ func resourceToProto(resource resourcedomain.Resource) *walletv1.Resource {
WalletAssetAmount: resource.WalletAssetAmount, WalletAssetAmount: resource.WalletAssetAmount,
PriceType: resource.PriceType, PriceType: resource.PriceType,
CoinPrice: resource.CoinPrice, CoinPrice: resource.CoinPrice,
GiftPointAmount: resource.GiftPointAmount, GiftPointAmount: 0,
UsageScopes: resource.UsageScopes, UsageScopes: resource.UsageScopes,
AssetUrl: resource.AssetURL, AssetUrl: resource.AssetURL,
PreviewUrl: resource.PreviewURL, PreviewUrl: resource.PreviewURL,
@ -669,7 +670,7 @@ func giftConfigToProto(gift resourcedomain.GiftConfig) *walletv1.GiftConfig {
PriceVersion: gift.PriceVersion, PriceVersion: gift.PriceVersion,
ChargeAssetType: gift.ChargeAssetType, ChargeAssetType: gift.ChargeAssetType,
CoinPrice: gift.CoinPrice, CoinPrice: gift.CoinPrice,
GiftPointAmount: gift.GiftPointAmount, GiftPointAmount: 0,
HeatValue: gift.HeatValue, HeatValue: gift.HeatValue,
GiftTypeCode: gift.GiftTypeCode, GiftTypeCode: gift.GiftTypeCode,
EffectiveFromMs: gift.EffectiveFromMS, EffectiveFromMs: gift.EffectiveFromMS,