feat: 接入旧站管理端并完整迁移后台能力
This commit is contained in:
@@ -121,7 +121,9 @@ type HomeBannerHotspot struct {
|
||||
|
||||
type PublishOptionsDTO struct {
|
||||
// SchemaVersion 发布选项结构版本;缺省 0 代表未版本化的历史配置。
|
||||
// UnlockDemulanEnabled 发布页是否展示「是否解锁德穆兰」选项。
|
||||
SchemaVersion int `json:"schema_version,omitempty"`
|
||||
UnlockDemulanEnabled bool `json:"unlock_demulan_enabled"`
|
||||
NextSeasonPassEnabled bool `json:"next_season_pass_enabled"`
|
||||
ServerOptions []string `json:"server_options"`
|
||||
FaceOptions []string `json:"face_options"`
|
||||
@@ -166,6 +168,9 @@ type PublishScreenshotSlot struct {
|
||||
type PublishPriceConfig struct {
|
||||
DepositPlaceholder string `json:"deposit_placeholder"`
|
||||
DepositHint string `json:"deposit_hint"`
|
||||
DepositTooltip string `json:"deposit_tooltip"`
|
||||
PremiumZoneTooltip string `json:"premium_zone_tooltip"`
|
||||
CompensationNote string `json:"compensation_note"`
|
||||
PricePlaceholder string `json:"price_placeholder"`
|
||||
RatioDescription string `json:"ratio_description"`
|
||||
}
|
||||
@@ -186,9 +191,11 @@ type PublishDepositSkinGroupRule struct {
|
||||
}
|
||||
|
||||
type PublishRatioConfig struct {
|
||||
InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"`
|
||||
ConfigItems []PublishRatioConfigItem `json:"config_items"`
|
||||
CoinCorrections []PublishCoinCorrection `json:"coin_corrections"`
|
||||
QualityOwnerRatioDiscount float64 `json:"quality_owner_ratio_discount"`
|
||||
PremiumReviewRatioMarkup float64 `json:"premium_review_ratio_markup"`
|
||||
InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"`
|
||||
ConfigItems []PublishRatioConfigItem `json:"config_items"`
|
||||
CoinCorrections []PublishCoinCorrection `json:"coin_corrections"`
|
||||
}
|
||||
|
||||
type PublishInsuranceBaseRatio struct {
|
||||
|
||||
@@ -9,6 +9,21 @@ import (
|
||||
const defaultBannerHotspotHoverScale = 1.06
|
||||
const maxBannerHotspotHoverScale = 1.5
|
||||
|
||||
func migrateInsuranceBillingCopy(value string) string {
|
||||
value = strings.Replace(
|
||||
value,
|
||||
"注:选择此方案,需额外抽取订单佣金的8.0%作为服务费:",
|
||||
"无忧保险服务费按实际纯币结算金额计算,额外物品不计入计费基数;具体费率以当前配置为准。",
|
||||
1,
|
||||
)
|
||||
return strings.Replace(
|
||||
value,
|
||||
"并从卖家实际到手租金中扣除。",
|
||||
"并按实际纯币结算金额计算,额外物品不计入保险基数。",
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
func normalizeAnnouncements(items []string) []string {
|
||||
normalized := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
@@ -231,6 +246,21 @@ func normalizePublishOptions(options *PublishOptionsDTO) {
|
||||
if options.PriceConfig.DepositHint == "" {
|
||||
options.PriceConfig.DepositHint = defaults.PriceConfig.DepositHint
|
||||
}
|
||||
if options.PriceConfig.DepositTooltip == "" {
|
||||
options.PriceConfig.DepositTooltip = defaults.PriceConfig.DepositTooltip
|
||||
}
|
||||
if options.PriceConfig.PremiumZoneTooltip == "" {
|
||||
options.PriceConfig.PremiumZoneTooltip = defaults.PriceConfig.PremiumZoneTooltip
|
||||
} else {
|
||||
options.PriceConfig.PremiumZoneTooltip =
|
||||
migrateInsuranceBillingCopy(options.PriceConfig.PremiumZoneTooltip)
|
||||
}
|
||||
if options.PriceConfig.CompensationNote == "" {
|
||||
options.PriceConfig.CompensationNote = defaults.PriceConfig.CompensationNote
|
||||
} else {
|
||||
options.PriceConfig.CompensationNote =
|
||||
migrateInsuranceBillingCopy(options.PriceConfig.CompensationNote)
|
||||
}
|
||||
if options.PriceConfig.PricePlaceholder == "" {
|
||||
options.PriceConfig.PricePlaceholder = defaults.PriceConfig.PricePlaceholder
|
||||
}
|
||||
@@ -252,6 +282,19 @@ func normalizePublishOptions(options *PublishOptionsDTO) {
|
||||
if len(options.RatioConfig.CoinCorrections) == 0 {
|
||||
options.RatioConfig.CoinCorrections = defaults.RatioConfig.CoinCorrections
|
||||
}
|
||||
options.RatioConfig.PremiumReviewRatioMarkup = normalizePremiumReviewRatioMarkup(
|
||||
options.RatioConfig.PremiumReviewRatioMarkup,
|
||||
)
|
||||
}
|
||||
|
||||
func normalizePremiumReviewRatioMarkup(value float64) float64 {
|
||||
if value < 0 || math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return 0
|
||||
}
|
||||
if value > 10 {
|
||||
return 10
|
||||
}
|
||||
return math.Round(value*10) / 10
|
||||
}
|
||||
|
||||
func normalizeSalePriceConfig(config *PublishSalePriceConfig) {
|
||||
|
||||
@@ -3,8 +3,10 @@ package systemconfig
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
// publishOptionsSchemaVersion 发布选项结构版本;v2 新增「是否解锁德穆兰」开关(默认关闭),
|
||||
// 并把「下赛季是否购买通行证」默认打开。
|
||||
publishOptionsSchemaVersion = 2
|
||||
publishOptionsConfigKey = "listing.publish_options"
|
||||
publishOptionsSchemaVersion = 1
|
||||
)
|
||||
|
||||
func defaultPublishOptionsConfigValue() string {
|
||||
@@ -18,7 +20,8 @@ func defaultPublishOptionsConfigValue() string {
|
||||
func DefaultPublishOptions() PublishOptionsDTO {
|
||||
return PublishOptionsDTO{
|
||||
SchemaVersion: publishOptionsSchemaVersion,
|
||||
NextSeasonPassEnabled: false,
|
||||
UnlockDemulanEnabled: false,
|
||||
NextSeasonPassEnabled: true,
|
||||
ServerOptions: []string{"QQ", "微信", "Steam"},
|
||||
FaceOptions: []string{"是", "否"},
|
||||
RankOptions: []string{"青铜", "白银", "黄金", "铂金", "钻石", "黑鹰", "三角洲巅峰"},
|
||||
@@ -90,8 +93,38 @@ func DefaultPublishOptions() PublishOptionsDTO {
|
||||
PriceConfig: PublishPriceConfig{
|
||||
DepositPlaceholder: "温馨提示:本押金用于保障账号安全。若买家在租用期间,使用非纯币类道具/资源(如消耗品、材料等)或导致账号被封禁,相关损失将直接从押金中扣除进行赔付。押金过高会延长出租等待时间(请自行衡量)感谢理解。",
|
||||
DepositHint: "租客缴纳,封号必赔付",
|
||||
PricePlaceholder: "填写币数、保险、体力和负重后自动计算;发布总价=纯币基础价+额外消耗品",
|
||||
RatioDescription: "1:xx 表示 1 元人民币(RMB)等价兑换 xx 万哈夫币",
|
||||
DepositTooltip: `1. 系统推荐押金(基础覆盖)
|
||||
|
||||
系统将根据您填写的出租皮肤、特勤处等级等信息,自动计算并生成一个推荐押金额度。该额度已覆盖上述主要资产的合理市场价值,可作为押金设置的基础参考。
|
||||
|
||||
2. 系统推荐押金未覆盖项目(需自行评估)
|
||||
|
||||
请注意,系统推荐押金暂不包含以下资产类型,您需要根据实际情况额外评估其价值:
|
||||
|
||||
- 收藏室内的稀有物品
|
||||
- 其他具有个人纪念意义或情怀价值的虚拟资产(如限定称号、绝版道具等)
|
||||
|
||||
3. 未出租价值折算规则(重点)
|
||||
|
||||
请在「收藏室及不出租部分哈弗币价值」中填写收藏室等不出租资产折合的哈弗币。系统按 1元=50万 计算参考值,并自动更新推荐押金:低于参考值时覆盖为参考值,否则追加参考值的 50%。
|
||||
|
||||
示例:基础推荐押金 200 元、收藏室价值 200M → 参考值 400 元,200<400,推荐押金更新为 400 元。
|
||||
|
||||
目的:该举能够有效覆盖租客可能出现的洗号、违规操作导致账号封禁等风险带来的损失,为您提供充分的风险缓冲。
|
||||
|
||||
4. 额外保障选项
|
||||
|
||||
若您在设置足额押金后仍感不放心,可选择购买平台提供的《全额包赔服务》。一旦发生封号损失,将获得账号市场价值的全额赔付,让您的出租过程后顾无忧。`,
|
||||
PremiumZoneTooltip: `极品专区是面向优质号主和高价值账号的专属展示区。
|
||||
|
||||
1. 发布条件:仅已认证且有效的优质号主可以开启;未开通时需先缴纳平台要求的信用保证金并完成优质号主认证。
|
||||
2. 专区展示:商品审核通过后将在首页“极品专区”展示,获得更聚焦的高端账号曝光。
|
||||
3. 价格规则:系统会按后台配置在基础比例上计算极品专区专享比例,审核页展示的最终价格通过后不再改价。
|
||||
4. 保险服务:可选择无忧租号保险,服务费率按优质号主及极品专区的当前配置执行,并按实际纯币结算金额计算,额外物品不计入保险基数。
|
||||
5. 责任说明:开启专区不会免除商品审核、资料真实性及发布协议中的责任,请如实填写账号资产与出租条件。`,
|
||||
CompensationNote: "普通赔付方案如下:账号在租期内或祖期外出现封禁,追回、洗号等情况,最高可获赔全款相金+押金\n\n无忧保险赔付方案:按账号市场价值进行赔付(可参照螃蟹,盼之等主流交易平台)。无忧保险服务费按实际纯币结算金额计算,额外物品不计入计费基数;具体费率以当前配置为准。押金须达到推荐押金方可购买。",
|
||||
PricePlaceholder: "填写币数、保险、体力和负重后自动计算;发布总价=纯币基础价+额外消耗品",
|
||||
RatioDescription: "1:xx 表示 1 元人民币(RMB)等价兑换 xx 万哈夫币",
|
||||
},
|
||||
DepositRecommend: PublishDepositRecommend{
|
||||
BaseAmount: 50,
|
||||
@@ -102,6 +135,8 @@ func DefaultPublishOptions() PublishOptionsDTO {
|
||||
},
|
||||
},
|
||||
RatioConfig: PublishRatioConfig{
|
||||
QualityOwnerRatioDiscount: 1,
|
||||
PremiumReviewRatioMarkup: 1,
|
||||
InsuranceBaseRatios: []PublishInsuranceBaseRatio{
|
||||
{Insurance: "3*3", Ratio: 40},
|
||||
{Insurance: "2*3", Ratio: 42},
|
||||
|
||||
@@ -106,6 +106,200 @@ func TestNormalizePublishOptionsValuePreservesCurrentSchema(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueDefaultsQualityOwnerRatioDiscount(t *testing.T) {
|
||||
legacy := `{
|
||||
"schema_version": 2,
|
||||
"ratio_config": {
|
||||
"insurance_base_ratios": [{"insurance": "3*3", "ratio": 40}]
|
||||
}
|
||||
}`
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(legacy)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if got.RatioConfig.QualityOwnerRatioDiscount != 1 {
|
||||
t.Fatalf(
|
||||
"quality_owner_ratio_discount = %v, want 1",
|
||||
got.RatioConfig.QualityOwnerRatioDiscount,
|
||||
)
|
||||
}
|
||||
if got.RatioConfig.PremiumReviewRatioMarkup != 1 {
|
||||
t.Fatalf(
|
||||
"premium_review_ratio_markup = %v, want 1",
|
||||
got.RatioConfig.PremiumReviewRatioMarkup,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueDefaultsDepositTooltip(t *testing.T) {
|
||||
normalized, err := normalizePublishOptionsValue(`{"schema_version":2}`)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &options); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !strings.Contains(options.PriceConfig.DepositTooltip, "系统推荐押金(基础覆盖)") {
|
||||
t.Fatalf("押金说明默认文案缺失: %q", options.PriceConfig.DepositTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueDefaultsCompensationNote(t *testing.T) {
|
||||
normalized, err := normalizePublishOptionsValue(`{"schema_version":2}`)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &options); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !strings.Contains(options.PriceConfig.CompensationNote, "普通赔付方案如下") {
|
||||
t.Fatalf("赔付方案说明默认文案缺失: %q", options.PriceConfig.CompensationNote)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueDefaultsPremiumZoneTooltip(t *testing.T) {
|
||||
normalized, err := normalizePublishOptionsValue(`{"schema_version":2}`)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var options PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &options); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !strings.Contains(options.PriceConfig.PremiumZoneTooltip, "极品专区是面向优质号主") {
|
||||
t.Fatalf("极品专区说明默认文案缺失: %q", options.PriceConfig.PremiumZoneTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueMigratesInsuranceBillingBaseCopy(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
options.PriceConfig.CompensationNote = "注:选择此方案,需额外抽取订单佣金的8.0%作为服务费:押金须达到推荐押金以上方可购买。"
|
||||
options.PriceConfig.PremiumZoneTooltip = "4. 保险服务:服务费率按当前配置执行,并从卖家实际到手租金中扣除。"
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
t.Fatalf("构造配置失败: %v", err)
|
||||
}
|
||||
normalized, err := normalizePublishOptionsValue(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !strings.Contains(got.PriceConfig.CompensationNote, "按实际纯币结算金额计算") {
|
||||
t.Fatalf("赔付说明未迁移纯币计费口径: %q", got.PriceConfig.CompensationNote)
|
||||
}
|
||||
if strings.Contains(got.PriceConfig.CompensationNote, "订单佣金") {
|
||||
t.Fatalf("赔付说明仍含旧计费口径: %q", got.PriceConfig.CompensationNote)
|
||||
}
|
||||
if !strings.Contains(got.PriceConfig.PremiumZoneTooltip, "额外物品不计入保险基数") {
|
||||
t.Fatalf("专区说明未迁移纯币计费口径: %q", got.PriceConfig.PremiumZoneTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValuePreservesExplicitZeroPremiumReviewRatioMarkup(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
options.RatioConfig.PremiumReviewRatioMarkup = 0
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
t.Fatalf("构造配置失败: %v", err)
|
||||
}
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if got.RatioConfig.PremiumReviewRatioMarkup != 0 {
|
||||
t.Fatalf(
|
||||
"premium_review_ratio_markup = %v, want explicit 0",
|
||||
got.RatioConfig.PremiumReviewRatioMarkup,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValuePreservesExplicitZeroQualityOwnerDiscount(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
options.RatioConfig.QualityOwnerRatioDiscount = 0
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
t.Fatalf("构造配置失败: %v", err)
|
||||
}
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if got.RatioConfig.QualityOwnerRatioDiscount != 0 {
|
||||
t.Fatalf(
|
||||
"quality_owner_ratio_discount = %v, want explicit 0",
|
||||
got.RatioConfig.QualityOwnerRatioDiscount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueMigratesSchemaV1Switches(t *testing.T) {
|
||||
legacy := `{"schema_version":1,"next_season_pass_enabled":false,"server_options":["QQ"]}`
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(legacy)
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if got.SchemaVersion != publishOptionsSchemaVersion {
|
||||
t.Fatalf("schema_version = %d, want %d", got.SchemaVersion, publishOptionsSchemaVersion)
|
||||
}
|
||||
if got.UnlockDemulanEnabled {
|
||||
t.Fatal("v1 升级后「是否解锁德穆兰」应默认关闭")
|
||||
}
|
||||
if !got.NextSeasonPassEnabled {
|
||||
t.Fatal("v1 升级后「下赛季是否购买通行证」应默认打开")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueKeepsSchemaV2Switches(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
options.UnlockDemulanEnabled = true
|
||||
options.NextSeasonPassEnabled = false
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
t.Fatalf("构造配置失败: %v", err)
|
||||
}
|
||||
|
||||
normalized, err := normalizePublishOptionsValue(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("规范化失败: %v", err)
|
||||
}
|
||||
var got PublishOptionsDTO
|
||||
if err := json.Unmarshal([]byte(normalized), &got); err != nil {
|
||||
t.Fatalf("解析规范化结果失败: %v", err)
|
||||
}
|
||||
if !got.UnlockDemulanEnabled {
|
||||
t.Fatal("v2 配置中后台手动开启的德穆兰开关不应被覆盖")
|
||||
}
|
||||
if got.NextSeasonPassEnabled {
|
||||
t.Fatal("v2 配置中后台手动关闭的通行证开关不应被覆盖")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizePublishOptionsValueRepairsMojibake(t *testing.T) {
|
||||
options := DefaultPublishOptions()
|
||||
raw, err := json.Marshal(options)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"hfb_jysh/backend/internal/auditlog"
|
||||
"hfb_jysh/backend/internal/model"
|
||||
"hfb_jysh/backend/internal/modules/qualityowner"
|
||||
"hfb_jysh/backend/internal/modules/rentergrowth"
|
||||
smsnode "hfb_jysh/backend/internal/modules/sms"
|
||||
|
||||
@@ -39,6 +40,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
|
||||
{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
|
||||
{Key: "listing.publish_cooldown_minutes", Value: "5", Description: "同一用户两次新增发布账号的最小间隔分钟数(0 表示关闭)"},
|
||||
{Key: qualityowner.ConfigKey, Value: qualityowner.DefaultConfigValue(), Description: "优质号主保证金、开通协议与无忧租号保险费率配置 JSON"},
|
||||
{Key: listingPublishAgreementsConfigKey, Value: defaultListingPublishAgreementsConfigValue(), Description: "发布账号前协议配置 JSON"},
|
||||
{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
|
||||
{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
|
||||
@@ -89,6 +91,7 @@ var adminVisibleConfigKeys = []string{
|
||||
"order.pending_payment_timeout_minutes",
|
||||
"order.return_overdue_grace_minutes",
|
||||
"profile.post_rental_notice",
|
||||
qualityowner.ConfigKey,
|
||||
"renter.growth_level_rules",
|
||||
"sms.business_templates",
|
||||
}
|
||||
@@ -129,6 +132,12 @@ func (r *Repository) List(ctx context.Context) ([]ConfigDTO, error) {
|
||||
}
|
||||
items := make([]ConfigDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if row.Key == qualityowner.ConfigKey {
|
||||
rules, err := qualityowner.ParseConfigValue(row.Value)
|
||||
if err == nil {
|
||||
row.Value, _ = qualityowner.MarshalConfig(rules)
|
||||
}
|
||||
}
|
||||
items = append(items, toDTO(row))
|
||||
}
|
||||
return items, nil
|
||||
@@ -328,6 +337,15 @@ func normalizePublishOptionsValue(value string) (string, error) {
|
||||
if err := json.Unmarshal([]byte(value), &fields); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !hasNestedJSONField(fields, "ratio_config", "quality_owner_ratio_discount") {
|
||||
options.RatioConfig.QualityOwnerRatioDiscount =
|
||||
DefaultPublishOptions().RatioConfig.QualityOwnerRatioDiscount
|
||||
}
|
||||
if !hasNestedJSONField(fields, "ratio_config", "premium_review_ratio_markup") {
|
||||
options.RatioConfig.PremiumReviewRatioMarkup =
|
||||
DefaultPublishOptions().RatioConfig.PremiumReviewRatioMarkup
|
||||
}
|
||||
originalVersion := options.SchemaVersion
|
||||
if options.SchemaVersion < publishOptionsSchemaVersion {
|
||||
if hasLegacyPublishQuantityItem(options.QuantityItems) {
|
||||
options = DefaultPublishOptions()
|
||||
@@ -340,6 +358,12 @@ func normalizePublishOptionsValue(value string) (string, error) {
|
||||
} else {
|
||||
normalizePublishOptions(&options)
|
||||
}
|
||||
// v2 一次性迁移:新增「是否解锁德穆兰」开关并默认关闭,同时打开「下赛季是否购买通行证」。
|
||||
// 已经是 v2 的配置不再覆盖,保留后台手动调整的结果。
|
||||
if originalVersion < 2 {
|
||||
options.UnlockDemulanEnabled = false
|
||||
options.NextSeasonPassEnabled = true
|
||||
}
|
||||
options.SchemaVersion = publishOptionsSchemaVersion
|
||||
raw, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
@@ -348,6 +372,19 @@ func normalizePublishOptionsValue(value string) (string, error) {
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
func hasNestedJSONField(fields map[string]json.RawMessage, parent, child string) bool {
|
||||
raw, ok := fields[parent]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
var nested map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &nested); err != nil {
|
||||
return false
|
||||
}
|
||||
_, ok = nested[child]
|
||||
return ok
|
||||
}
|
||||
|
||||
func hasLegacyPublishQuantityItem(items []PublishQuantityItem) bool {
|
||||
for _, item := range items {
|
||||
if item.Key == "kit5" {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hfb_jysh/backend/internal/modules/qualityowner"
|
||||
"hfb_jysh/backend/internal/modules/rentergrowth"
|
||||
smsnode "hfb_jysh/backend/internal/modules/sms"
|
||||
)
|
||||
@@ -55,6 +56,17 @@ func (s *Service) Update(ctx context.Context, actorID uint64, key string, req Up
|
||||
}
|
||||
req.Value = value
|
||||
}
|
||||
if key == qualityowner.ConfigKey {
|
||||
cfg, err := qualityowner.ParseConfigValue(req.Value)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidConfig
|
||||
}
|
||||
value, err := qualityowner.MarshalConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidConfig
|
||||
}
|
||||
req.Value = value
|
||||
}
|
||||
return s.repo.Update(ctx, actorID, key, req, meta)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user