补充 world geo nginx snippet、OpenAPI/geo 热更新脚本,以及 popadmin package_name 定价补丁,便于运维对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
4.1 KiB
Java
97 lines
4.1 KiB
Java
package com.wyrs.admin.service.config;
|
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.wyrs.admin.config.WyrsConfigProperties;
|
|
import com.wyrs.admin.entity.MembershipPrice;
|
|
import com.wyrs.admin.util.PackageNameContext;
|
|
import java.math.BigDecimal;
|
|
import java.util.Comparator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
@Service
|
|
public class PricesConfigService extends AbstractEntityListConfigService<MembershipPrice> {
|
|
private static final TypeReference<EntityListStore<MembershipPrice>> TYPE =
|
|
new TypeReference<EntityListStore<MembershipPrice>>() {};
|
|
|
|
public PricesConfigService(WyrsConfigProperties config, ObjectMapper objectMapper) {
|
|
super(config.resolveFile("prices.json"), objectMapper, TYPE, MembershipPrice::getId, MembershipPrice::getAppCode);
|
|
}
|
|
|
|
public List<MembershipPrice> findByAppCode(String appCode) {
|
|
// Admin: all packages under appCode
|
|
return this.findByAppCodeOrderByPriceAsc(appCode, false, null);
|
|
}
|
|
|
|
public List<MembershipPrice> findByAppCodeAndEnabledTrueOrderByPriceAsc(String appCode) {
|
|
// Client: filter by X-Wyrs-Package / packageName
|
|
return this.findByAppCodeOrderByPriceAsc(appCode, true, PackageNameContext.get());
|
|
}
|
|
|
|
private List<MembershipPrice> findByAppCodeOrderByPriceAsc(String appCode, boolean enabledOnly, String packageName) {
|
|
Object object = this.lock;
|
|
synchronized (object) {
|
|
List<MembershipPrice> base = this.store.getItems().stream()
|
|
.filter(p -> appCode.equals(p.getAppCode()))
|
|
.filter(p -> !enabledOnly || Boolean.TRUE.equals(p.getEnabled()))
|
|
.sorted(Comparator.comparing(MembershipPrice::getPrice, Comparator.nullsLast(BigDecimal::compareTo)))
|
|
.toList();
|
|
return filterByPackage(base, packageName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Empty packageName: return all.
|
|
* Non-empty: prefer exact package match; else fall back to blank packageName defaults.
|
|
*/
|
|
public static List<MembershipPrice> filterByPackage(List<MembershipPrice> items, String packageName) {
|
|
if (packageName == null || packageName.isBlank()) {
|
|
return items;
|
|
}
|
|
String want = packageName.trim().toLowerCase(Locale.ROOT);
|
|
List<MembershipPrice> exact = items.stream()
|
|
.filter(p -> {
|
|
String pkg = p.getPackageName();
|
|
return pkg != null && !pkg.isBlank() && pkg.trim().toLowerCase(Locale.ROOT).equals(want);
|
|
})
|
|
.toList();
|
|
if (!exact.isEmpty()) {
|
|
return exact;
|
|
}
|
|
return items.stream()
|
|
.filter(p -> {
|
|
String pkg = p.getPackageName();
|
|
return pkg == null || pkg.isBlank();
|
|
})
|
|
.toList();
|
|
}
|
|
|
|
public MembershipPrice save(MembershipPrice price) {
|
|
if (price != null) {
|
|
if (price.getPackageName() == null) {
|
|
price.setPackageName("");
|
|
}
|
|
if (price.getBillingType() == null || price.getBillingType().isBlank()) {
|
|
Integer days = price.getDurationDays();
|
|
price.setBillingType(days != null && days > 0 ? "subscription" : "one_time");
|
|
} else {
|
|
String bt = price.getBillingType().trim().toLowerCase(Locale.ROOT);
|
|
if ("sub".equals(bt) || "renew".equals(bt) || "auto".equals(bt)) {
|
|
bt = "subscription";
|
|
}
|
|
if ("once".equals(bt) || "onetime".equals(bt) || "iap".equals(bt)) {
|
|
bt = "one_time";
|
|
}
|
|
price.setBillingType("subscription".equals(bt) ? "subscription" : "one_time");
|
|
}
|
|
if (price.getChannelProductCodes() == null) {
|
|
price.setChannelProductCodes(new LinkedHashMap<>());
|
|
}
|
|
}
|
|
return this.save(price, MembershipPrice::setId);
|
|
}
|
|
}
|