优化烽火地带最小等级
This commit is contained in:
@@ -313,6 +313,10 @@ func writeListingError(c *gin.Context, err error) {
|
|||||||
response.BadRequest(c, "哈夫币数量不正确")
|
response.BadRequest(c, "哈夫币数量不正确")
|
||||||
case errors.Is(err, ErrMissingScreenshot):
|
case errors.Is(err, ErrMissingScreenshot):
|
||||||
response.BadRequest(c, "请至少上传一张账号截图")
|
response.BadRequest(c, "请至少上传一张账号截图")
|
||||||
|
case isFireLevelTooLow(err):
|
||||||
|
var levelErr FireLevelTooLowError
|
||||||
|
errors.As(err, &levelErr)
|
||||||
|
response.BadRequest(c, "烽火等级低于"+strconv.Itoa(levelErr.Min)+"级的号无法发布")
|
||||||
case errors.Is(err, ErrInvalidInput):
|
case errors.Is(err, ErrInvalidInput):
|
||||||
response.BadRequest(c, "发布信息不符合规则")
|
response.BadRequest(c, "发布信息不符合规则")
|
||||||
case errors.Is(err, ErrListingLocked):
|
case errors.Is(err, ErrListingLocked):
|
||||||
@@ -323,3 +327,8 @@ func writeListingError(c *gin.Context, err error) {
|
|||||||
response.Error(c, http.StatusInternalServerError, "internal_error", "发布服务暂时不可用")
|
response.Error(c, http.StatusInternalServerError, "internal_error", "发布服务暂时不可用")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isFireLevelTooLow(err error) bool {
|
||||||
|
var levelErr FireLevelTooLowError
|
||||||
|
return errors.As(err, &levelErr)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package listing
|
package listing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -27,7 +28,19 @@ type ConfigReader interface {
|
|||||||
FindValue(key string) (string, error)
|
FindValue(key string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewRequiredConfigKey = "listing.review_required"
|
const (
|
||||||
|
reviewRequiredConfigKey = "listing.review_required"
|
||||||
|
publishOptionsConfigKey = "listing.publish_options"
|
||||||
|
defaultFireLevelMin = 38
|
||||||
|
)
|
||||||
|
|
||||||
|
type FireLevelTooLowError struct {
|
||||||
|
Min int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e FireLevelTooLowError) Error() string {
|
||||||
|
return "fire level too low"
|
||||||
|
}
|
||||||
|
|
||||||
func NewService(repo *Repository, config ConfigReader) *Service {
|
func NewService(repo *Repository, config ConfigReader) *Service {
|
||||||
return &Service{repo: repo, config: config}
|
return &Service{repo: repo, config: config}
|
||||||
@@ -37,7 +50,11 @@ func (s *Service) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error)
|
|||||||
if s.repo == nil {
|
if s.repo == nil {
|
||||||
return nil, ErrDependencyUnavailable
|
return nil, ErrDependencyUnavailable
|
||||||
}
|
}
|
||||||
if err := validateRequest(req); err != nil {
|
rules, err := s.publishRules()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := validateRequest(req, rules); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reviewRequired, err := s.reviewRequired()
|
reviewRequired, err := s.reviewRequired()
|
||||||
@@ -51,7 +68,11 @@ func (s *Service) Update(ownerID uint64, id uint64, req UpdateRequest) (*Listing
|
|||||||
if s.repo == nil {
|
if s.repo == nil {
|
||||||
return nil, ErrDependencyUnavailable
|
return nil, ErrDependencyUnavailable
|
||||||
}
|
}
|
||||||
if err := validateRequest(req); err != nil {
|
rules, err := s.publishRules()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := validateRequest(req, rules); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reviewRequired, err := s.reviewRequired()
|
reviewRequired, err := s.reviewRequired()
|
||||||
@@ -165,7 +186,11 @@ func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
|||||||
return s.repo.FindMine(ownerID, id)
|
return s.repo.FindMine(ownerID, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRequest(req CreateRequest) error {
|
type publishRules struct {
|
||||||
|
FireLevelMin int
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRequest(req CreateRequest, rules publishRules) error {
|
||||||
if strings.TrimSpace(req.Title) == "" {
|
if strings.TrimSpace(req.Title) == "" {
|
||||||
return ErrMissingTitle
|
return ErrMissingTitle
|
||||||
}
|
}
|
||||||
@@ -184,9 +209,33 @@ func validateRequest(req CreateRequest) error {
|
|||||||
if !hasScreenshotURL(req.ScreenshotURLS) {
|
if !hasScreenshotURL(req.ScreenshotURLS) {
|
||||||
return ErrMissingScreenshot
|
return ErrMissingScreenshot
|
||||||
}
|
}
|
||||||
|
if fireLevel, ok := readFireLevel(req.AssetSummary); ok && fireLevel < rules.FireLevelMin {
|
||||||
|
return FireLevelTooLowError{Min: rules.FireLevelMin}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readFireLevel(summary map[string]any) (int, bool) {
|
||||||
|
if summary == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
value, ok := summary["fire_level"]
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
switch current := value.(type) {
|
||||||
|
case float64:
|
||||||
|
return int(current), true
|
||||||
|
case int:
|
||||||
|
return current, true
|
||||||
|
case string:
|
||||||
|
parsed, err := strconv.Atoi(strings.TrimSpace(current))
|
||||||
|
return parsed, err == nil
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func hasScreenshotURL(urls []string) bool {
|
func hasScreenshotURL(urls []string) bool {
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
if strings.TrimSpace(url) != "" {
|
if strings.TrimSpace(url) != "" {
|
||||||
@@ -210,3 +259,24 @@ func (s *Service) reviewRequired() (bool, error) {
|
|||||||
}
|
}
|
||||||
return required, nil
|
return required, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) publishRules() (publishRules, error) {
|
||||||
|
rules := publishRules{FireLevelMin: defaultFireLevelMin}
|
||||||
|
if s.config == nil {
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
value, err := s.config.FindValue(publishOptionsConfigKey)
|
||||||
|
if err != nil {
|
||||||
|
return rules, err
|
||||||
|
}
|
||||||
|
var raw struct {
|
||||||
|
FireLevelMin int `json:"fire_level_min"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(value), &raw); err != nil {
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
if raw.FireLevelMin > 0 {
|
||||||
|
rules.FireLevelMin = raw.FireLevelMin
|
||||||
|
}
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ type PublishOptionsDTO struct {
|
|||||||
ScreenshotSlots []PublishScreenshotSlot `json:"screenshot_slots"`
|
ScreenshotSlots []PublishScreenshotSlot `json:"screenshot_slots"`
|
||||||
BanRecordOptions []string `json:"ban_record_options"`
|
BanRecordOptions []string `json:"ban_record_options"`
|
||||||
BanEvidenceOptions []string `json:"ban_evidence_options"`
|
BanEvidenceOptions []string `json:"ban_evidence_options"`
|
||||||
|
FireLevelMin int `json:"fire_level_min"`
|
||||||
PriceConfig PublishPriceConfig `json:"price_config"`
|
PriceConfig PublishPriceConfig `json:"price_config"`
|
||||||
RatioConfig PublishRatioConfig `json:"ratio_config"`
|
RatioConfig PublishRatioConfig `json:"ratio_config"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func DefaultPublishOptions() PublishOptionsDTO {
|
|||||||
},
|
},
|
||||||
BanRecordOptions: []string{"无封禁记录", "有封禁记录"},
|
BanRecordOptions: []string{"无封禁记录", "有封禁记录"},
|
||||||
BanEvidenceOptions: []string{"有封禁记录"},
|
BanEvidenceOptions: []string{"有封禁记录"},
|
||||||
|
FireLevelMin: 38,
|
||||||
PriceConfig: PublishPriceConfig{
|
PriceConfig: PublishPriceConfig{
|
||||||
DepositPlaceholder: "温馨提示:本押金用于保障账号安全。若买家在租用期间,使用非纯币类道具/资源(如消耗品、材料等)或导致账号被封禁,相关损失将直接从押金中扣除进行赔付。押金过高会延长出租等待时间(请自行衡量)感谢理解。",
|
DepositPlaceholder: "温馨提示:本押金用于保障账号安全。若买家在租用期间,使用非纯币类道具/资源(如消耗品、材料等)或导致账号被封禁,相关损失将直接从押金中扣除进行赔付。押金过高会延长出租等待时间(请自行衡量)感谢理解。",
|
||||||
PricePlaceholder: "填写币数、保险、体力和负重后自动计算",
|
PricePlaceholder: "填写币数、保险、体力和负重后自动计算",
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ func normalizePublishOptions(options *PublishOptionsDTO) {
|
|||||||
if len(options.BanEvidenceOptions) == 0 {
|
if len(options.BanEvidenceOptions) == 0 {
|
||||||
options.BanEvidenceOptions = defaults.BanEvidenceOptions
|
options.BanEvidenceOptions = defaults.BanEvidenceOptions
|
||||||
}
|
}
|
||||||
|
if options.FireLevelMin <= 0 {
|
||||||
|
options.FireLevelMin = defaults.FireLevelMin
|
||||||
|
}
|
||||||
if options.PriceConfig.DepositPlaceholder == "" {
|
if options.PriceConfig.DepositPlaceholder == "" {
|
||||||
options.PriceConfig.DepositPlaceholder = defaults.PriceConfig.DepositPlaceholder
|
options.PriceConfig.DepositPlaceholder = defaults.PriceConfig.DepositPlaceholder
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export interface ListingPublishOptions {
|
|||||||
screenshot_slots: PublishScreenshotSlot[]
|
screenshot_slots: PublishScreenshotSlot[]
|
||||||
ban_record_options: string[]
|
ban_record_options: string[]
|
||||||
ban_evidence_options: string[]
|
ban_evidence_options: string[]
|
||||||
|
fire_level_min: number
|
||||||
price_config: PublishPriceConfig
|
price_config: PublishPriceConfig
|
||||||
ratio_config: PublishRatioConfig
|
ratio_config: PublishRatioConfig
|
||||||
}
|
}
|
||||||
@@ -94,6 +95,7 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
|
|||||||
screenshot_slots: [],
|
screenshot_slots: [],
|
||||||
ban_record_options: [],
|
ban_record_options: [],
|
||||||
ban_evidence_options: [],
|
ban_evidence_options: [],
|
||||||
|
fire_level_min: 38,
|
||||||
price_config: {
|
price_config: {
|
||||||
deposit_placeholder: '',
|
deposit_placeholder: '',
|
||||||
price_placeholder: '',
|
price_placeholder: '',
|
||||||
@@ -125,6 +127,7 @@ export function mergeListingPublishOptions(options?: Partial<ListingPublishOptio
|
|||||||
screenshot_slots: normalizeScreenshotSlots(options?.screenshot_slots),
|
screenshot_slots: normalizeScreenshotSlots(options?.screenshot_slots),
|
||||||
ban_record_options: normalizeStringList(options?.ban_record_options),
|
ban_record_options: normalizeStringList(options?.ban_record_options),
|
||||||
ban_evidence_options: normalizeStringList(options?.ban_evidence_options),
|
ban_evidence_options: normalizeStringList(options?.ban_evidence_options),
|
||||||
|
fire_level_min: readPositiveInteger(options?.fire_level_min, 38),
|
||||||
price_config: normalizePriceConfig(options?.price_config),
|
price_config: normalizePriceConfig(options?.price_config),
|
||||||
ratio_config: normalizeRatioConfig(options?.ratio_config),
|
ratio_config: normalizeRatioConfig(options?.ratio_config),
|
||||||
}
|
}
|
||||||
@@ -248,6 +251,11 @@ function readNumber(value: unknown) {
|
|||||||
return Number.isFinite(parsed) ? parsed : 0
|
return Number.isFinite(parsed) ? parsed : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readPositiveInteger(value: unknown, fallback: number) {
|
||||||
|
const parsed = Math.trunc(readNumber(value))
|
||||||
|
return parsed > 0 ? parsed : fallback
|
||||||
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return typeof value === 'object' && value !== null
|
return typeof value === 'object' && value !== null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,6 +543,15 @@ function readError(error: unknown, fallback: string) {
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-block">
|
||||||
|
<div class="editor-block-title">
|
||||||
|
<strong>发布规则</strong>
|
||||||
|
</div>
|
||||||
|
<el-form-item label="最低烽火等级">
|
||||||
|
<el-input-number v-model="publishOptionsDraft.fire_level_min" :min="1" :step="1" class="full-control" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="editor-block">
|
<div class="editor-block">
|
||||||
<div class="editor-block-title">
|
<div class="editor-block-title">
|
||||||
<strong>押金与价格提示</strong>
|
<strong>押金与价格提示</strong>
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ const regionOptions = computed(() => publishOptions.value.region_options);
|
|||||||
const banRecordOptions = computed(() => publishOptions.value.ban_record_options);
|
const banRecordOptions = computed(() => publishOptions.value.ban_record_options);
|
||||||
const banEvidenceOptions = computed(() => publishOptions.value.ban_evidence_options);
|
const banEvidenceOptions = computed(() => publishOptions.value.ban_evidence_options);
|
||||||
const priceConfig = computed(() => publishOptions.value.price_config);
|
const priceConfig = computed(() => publishOptions.value.price_config);
|
||||||
|
const fireLevelMin = computed(() => publishOptions.value.fire_level_min || 38);
|
||||||
|
const fireLevelPlaceholder = computed(
|
||||||
|
() => `等级低于${fireLevelMin.value}级的号无法发布`
|
||||||
|
);
|
||||||
const ratioConfig = computed(() => publishOptions.value.ratio_config);
|
const ratioConfig = computed(() => publishOptions.value.ratio_config);
|
||||||
const skinGroups = computed(() => publishOptions.value.skin_groups);
|
const skinGroups = computed(() => publishOptions.value.skin_groups);
|
||||||
const quantityItems = computed(() => publishOptions.value.quantity_items);
|
const quantityItems = computed(() => publishOptions.value.quantity_items);
|
||||||
@@ -394,7 +398,9 @@ function validateForm() {
|
|||||||
if (coinMAmount.value <= 0) return "请填写哈夫币/M";
|
if (coinMAmount.value <= 0) return "请填写哈夫币/M";
|
||||||
if (!form.rank_level) return "请选择段位";
|
if (!form.rank_level) return "请选择段位";
|
||||||
if (!form.fire_level) return "请填写烽火等级";
|
if (!form.fire_level) return "请填写烽火等级";
|
||||||
if (Number(form.fire_level) < 38) return "烽火等级低于38级的号无法发布";
|
if (Number(form.fire_level) < fireLevelMin.value) {
|
||||||
|
return `烽火等级低于${fireLevelMin.value}级的号无法发布`;
|
||||||
|
}
|
||||||
if (!form.season_insurance) return "请选择赛季保险";
|
if (!form.season_insurance) return "请选择赛季保险";
|
||||||
if (!form.stamina_level) return "请选择体力等级";
|
if (!form.stamina_level) return "请选择体力等级";
|
||||||
if (!form.load_level) return "请选择负重等级";
|
if (!form.load_level) return "请选择负重等级";
|
||||||
@@ -642,7 +648,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
label="烽火等级"
|
label="烽火等级"
|
||||||
type="digit"
|
type="digit"
|
||||||
required
|
required
|
||||||
placeholder="等级低于38级的号无法发布"
|
:placeholder="fireLevelPlaceholder"
|
||||||
class="publish-field"
|
class="publish-field"
|
||||||
@update:model-value="handleFireLevelInput"
|
@update:model-value="handleFireLevelInput"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user