|
@@ -7,8 +7,10 @@ import com.pharmacopoeia.repository.BrandRepository;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Consumer;
|
|
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
@Service
|
|
@Service
|
|
@@ -23,59 +25,59 @@ public class BrandRecommendService {
|
|
|
this.brandRepository = brandRepository;
|
|
this.brandRepository = brandRepository;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== 层级匹配 ====================
|
|
|
|
|
|
|
+ // ==================== field constants ====================
|
|
|
|
|
+
|
|
|
|
|
+ private static final List<String> BRAND_CSV_COLS = List.of(
|
|
|
|
|
+ "name", "function_indication", "usage_dosage", "contraindication",
|
|
|
|
|
+ "ingredients", "properties", "specification", "adverse_reactions",
|
|
|
|
|
+ "precautions", "execution_standard", "storage", "jump_url", "description", "sort_order"
|
|
|
|
|
+ );
|
|
|
|
|
+ private static final String BRANDS_CSV_HEADER = String.join(",", BRAND_CSV_COLS);
|
|
|
|
|
+ private static final String RULES_CSV_HEADER = "keyword,brand_id,brand_name,tier,is_active";
|
|
|
|
|
+
|
|
|
|
|
+ // non-name, non-sort_order columns
|
|
|
|
|
+ private static final List<String> BRAND_TEXT_COLS = BRAND_CSV_COLS.stream()
|
|
|
|
|
+ .filter(c -> !"name".equals(c) && !"sort_order".equals(c))
|
|
|
|
|
+ .toList();
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== tier matching ====================
|
|
|
|
|
|
|
|
public List<Map<String, Object>> match(List<Map<String, Object>> sources, String fullAnswer) {
|
|
public List<Map<String, Object>> match(List<Map<String, Object>> sources, String fullAnswer) {
|
|
|
- // 仅加载 is_active=true 的规则,已停用的不参与匹配
|
|
|
|
|
List<BrandRecommendRule> rules = ruleRepository.findByIsActiveTrueOrderByTierAscKeywordAsc();
|
|
List<BrandRecommendRule> rules = ruleRepository.findByIsActiveTrueOrderByTierAscKeywordAsc();
|
|
|
- if (rules.isEmpty()) {
|
|
|
|
|
- return List.of();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return rules.isEmpty() ? List.of() : doMatch(rules, sources, fullAnswer);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> doMatch(List<BrandRecommendRule> rules,
|
|
|
|
|
+ List<Map<String, Object>> sources, String fullAnswer) {
|
|
|
Map<Integer, List<BrandRecommendRule>> byTier = rules.stream()
|
|
Map<Integer, List<BrandRecommendRule>> byTier = rules.stream()
|
|
|
.collect(Collectors.groupingBy(BrandRecommendRule::getTier, LinkedHashMap::new, Collectors.toList()));
|
|
.collect(Collectors.groupingBy(BrandRecommendRule::getTier, LinkedHashMap::new, Collectors.toList()));
|
|
|
|
|
|
|
|
- Set<String> sourceNames = sources.stream()
|
|
|
|
|
|
|
+ Set<String> texts = sources.stream()
|
|
|
.map(s -> (String) s.getOrDefault("name", ""))
|
|
.map(s -> (String) s.getOrDefault("name", ""))
|
|
|
.filter(n -> !n.isEmpty())
|
|
.filter(n -> !n.isEmpty())
|
|
|
- .collect(Collectors.toSet());
|
|
|
|
|
-
|
|
|
|
|
- for (Map.Entry<Integer, List<BrandRecommendRule>> entry : byTier.entrySet()) {
|
|
|
|
|
- List<Map<String, Object>> matches = findMatches(entry.getValue(), sourceNames);
|
|
|
|
|
- if (!matches.isEmpty()) {
|
|
|
|
|
- return matches;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 全文回退也按 tier 逐层尝试
|
|
|
|
|
- if (fullAnswer != null && !fullAnswer.isEmpty()) {
|
|
|
|
|
- for (Map.Entry<Integer, List<BrandRecommendRule>> entry : byTier.entrySet()) {
|
|
|
|
|
- List<Map<String, Object>> matches = findMatches(entry.getValue(), Set.of(fullAnswer));
|
|
|
|
|
- if (!matches.isEmpty()) {
|
|
|
|
|
- return matches;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return List.of();
|
|
|
|
|
|
|
+ .collect(Collectors.toCollection(HashSet::new));
|
|
|
|
|
+ Optional.ofNullable(fullAnswer).filter(a -> !a.isEmpty()).ifPresent(texts::add);
|
|
|
|
|
+
|
|
|
|
|
+ return byTier.values().stream()
|
|
|
|
|
+ .map(tierRules -> findMatches(tierRules, texts))
|
|
|
|
|
+ .filter(m -> !m.isEmpty())
|
|
|
|
|
+ .findFirst()
|
|
|
|
|
+ .orElse(List.of());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private List<Map<String, Object>> findMatches(List<BrandRecommendRule> rules, Set<String> targets) {
|
|
private List<Map<String, Object>> findMatches(List<BrandRecommendRule> rules, Set<String> targets) {
|
|
|
- // 一次性加载所有用到的 Brand
|
|
|
|
|
Set<Integer> brandIds = rules.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
Set<Integer> brandIds = rules.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
|
Map<Integer, Brand> brandMap = brandRepository.findAllById(brandIds).stream()
|
|
Map<Integer, Brand> brandMap = brandRepository.findAllById(brandIds).stream()
|
|
|
.collect(Collectors.toMap(Brand::getId, b -> b));
|
|
.collect(Collectors.toMap(Brand::getId, b -> b));
|
|
|
|
|
|
|
|
- Set<Integer> seenRuleIds = new HashSet<>(); // 规则去重(同规则只命中一次)
|
|
|
|
|
- Set<Integer> seenBrandIds = new HashSet<>(); // 品牌去重(同品牌只返回一次)
|
|
|
|
|
|
|
+ Set<Integer> seenRuleIds = new HashSet<>();
|
|
|
|
|
+ Set<Integer> seenBrandIds = new HashSet<>();
|
|
|
List<Map<String, Object>> result = new ArrayList<>();
|
|
List<Map<String, Object>> result = new ArrayList<>();
|
|
|
for (BrandRecommendRule rule : rules) {
|
|
for (BrandRecommendRule rule : rules) {
|
|
|
- for (String target : targets) {
|
|
|
|
|
- if (target.toLowerCase().contains(rule.getKeyword().toLowerCase())) {
|
|
|
|
|
- if (seenRuleIds.add(rule.getId()) && seenBrandIds.add(rule.getBrandId())) {
|
|
|
|
|
- Brand brand = brandMap.get(rule.getBrandId());
|
|
|
|
|
- result.add(buildBrandResult(rule, brand));
|
|
|
|
|
- }
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ String kwLower = rule.getKeyword().toLowerCase();
|
|
|
|
|
+ boolean hit = targets.stream().anyMatch(t -> t.toLowerCase().contains(kwLower));
|
|
|
|
|
+ if (hit && seenRuleIds.add(rule.getId()) && seenBrandIds.add(rule.getBrandId())) {
|
|
|
|
|
+ result.add(buildBrandResult(rule, brandMap.get(rule.getBrandId())));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
return result;
|
|
return result;
|
|
@@ -104,38 +106,26 @@ public class BrandRecommendService {
|
|
|
return m;
|
|
return m;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== 品牌 CRUD ====================
|
|
|
|
|
|
|
+ // ==================== brand CRUD ====================
|
|
|
|
|
|
|
|
public Map<String, Object> listBrands(String keyword, int page, int pageSize) {
|
|
public Map<String, Object> listBrands(String keyword, int page, int pageSize) {
|
|
|
List<Brand> all = brandRepository.findAllByOrderBySortOrderAsc();
|
|
List<Brand> all = brandRepository.findAllByOrderBySortOrderAsc();
|
|
|
- if (keyword != null && !keyword.isBlank()) {
|
|
|
|
|
- String kw = keyword.toLowerCase();
|
|
|
|
|
- all = all.stream()
|
|
|
|
|
- .filter(b -> b.getName().toLowerCase().contains(kw))
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
- }
|
|
|
|
|
- int total = all.size();
|
|
|
|
|
- int from = (page - 1) * pageSize;
|
|
|
|
|
- int to = Math.min(from + pageSize, total);
|
|
|
|
|
- List<Brand> pageItems = from < total ? all.subList(from, to) : List.of();
|
|
|
|
|
- return Map.of(
|
|
|
|
|
- "items", (Object) pageItems,
|
|
|
|
|
- "page", page,
|
|
|
|
|
- "page_size", pageSize,
|
|
|
|
|
- "total", total,
|
|
|
|
|
- "total_pages", (int) Math.ceil((double) total / pageSize)
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ Optional.ofNullable(keyword)
|
|
|
|
|
+ .filter(k -> !k.isBlank())
|
|
|
|
|
+ .ifPresent(k -> {
|
|
|
|
|
+ String kw = k.toLowerCase();
|
|
|
|
|
+ all.removeIf(b -> !b.getName().toLowerCase().contains(kw));
|
|
|
|
|
+ });
|
|
|
|
|
+ return paginate(all, page, pageSize);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> createBrand(Map<String, Object> body) {
|
|
public Map<String, Object> createBrand(Map<String, Object> body) {
|
|
|
- String name = (String) body.get("name");
|
|
|
|
|
- if (name == null || name.isBlank()) {
|
|
|
|
|
- throw new IllegalArgumentException("品牌名称不能为空");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ String name = require(body);
|
|
|
if (brandRepository.existsByNameAndIdNot(name.trim(), null)) {
|
|
if (brandRepository.existsByNameAndIdNot(name.trim(), null)) {
|
|
|
- throw new IllegalArgumentException("该品牌名称已存在");
|
|
|
|
|
|
|
+ throw new IllegalArgumentException("brand name already exists");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
Brand brand = Brand.builder()
|
|
Brand brand = Brand.builder()
|
|
|
.name(name.trim())
|
|
.name(name.trim())
|
|
|
.functionIndication(str(body, "function_indication"))
|
|
.functionIndication(str(body, "function_indication"))
|
|
@@ -150,98 +140,79 @@ public class BrandRecommendService {
|
|
|
.storage(str(body, "storage"))
|
|
.storage(str(body, "storage"))
|
|
|
.jumpUrl(str(body, "jump_url"))
|
|
.jumpUrl(str(body, "jump_url"))
|
|
|
.description(str(body, "description"))
|
|
.description(str(body, "description"))
|
|
|
- .sortOrder(body.get("sort_order") instanceof Number n ? n.intValue() : 0)
|
|
|
|
|
|
|
+ .sortOrder(intVal(body))
|
|
|
.isActive(true)
|
|
.isActive(true)
|
|
|
.build();
|
|
.build();
|
|
|
brand = brandRepository.save(brand);
|
|
brand = brandRepository.save(brand);
|
|
|
- return Map.of("ok", true, "id", brand.getId(), "message", "已创建");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "id", brand.getId(), "message", "created");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> updateBrand(int id, Map<String, Object> body) {
|
|
public Map<String, Object> updateBrand(int id, Map<String, Object> body) {
|
|
|
- var opt = brandRepository.findById(id);
|
|
|
|
|
- if (opt.isEmpty()) throw new IllegalArgumentException("未找到该品牌");
|
|
|
|
|
- Brand b = opt.get();
|
|
|
|
|
- String name = (String) body.get("name");
|
|
|
|
|
- if (name != null && !name.isBlank()) {
|
|
|
|
|
- if (brandRepository.existsByNameAndIdNot(name.trim(), id)) {
|
|
|
|
|
- throw new IllegalArgumentException("该品牌名称已存在");
|
|
|
|
|
- }
|
|
|
|
|
- b.setName(name.trim());
|
|
|
|
|
- }
|
|
|
|
|
- applyStr(body, "function_indication", b::setFunctionIndication);
|
|
|
|
|
- applyStr(body, "usage_dosage", b::setUsageDosage);
|
|
|
|
|
- applyStr(body, "contraindication", b::setContraindication);
|
|
|
|
|
- applyStr(body, "ingredients", b::setIngredients);
|
|
|
|
|
- applyStr(body, "properties", b::setProperties);
|
|
|
|
|
- applyStr(body, "specification", b::setSpecification);
|
|
|
|
|
- applyStr(body, "adverse_reactions", b::setAdverseReactions);
|
|
|
|
|
- applyStr(body, "precautions", b::setPrecautions);
|
|
|
|
|
- applyStr(body, "execution_standard", b::setExecutionStandard);
|
|
|
|
|
- applyStr(body, "storage", b::setStorage);
|
|
|
|
|
- applyStr(body, "jump_url", b::setJumpUrl);
|
|
|
|
|
- applyStr(body, "description", b::setDescription);
|
|
|
|
|
- Optional.ofNullable((Number) body.get("sort_order")).ifPresent(n -> b.setSortOrder(n.intValue()));
|
|
|
|
|
- Optional.ofNullable((Boolean) body.get("is_active")).ifPresent(b::setIsActive);
|
|
|
|
|
|
|
+ Brand b = brandRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("brand not found"));
|
|
|
|
|
+
|
|
|
|
|
+ Optional.ofNullable(body.get("name"))
|
|
|
|
|
+ .map(Object::toString).map(String::trim).filter(n -> !n.isBlank())
|
|
|
|
|
+ .ifPresent(n -> {
|
|
|
|
|
+ if (brandRepository.existsByNameAndIdNot(n, id)) {
|
|
|
|
|
+ throw new IllegalArgumentException("brand name already exists");
|
|
|
|
|
+ }
|
|
|
|
|
+ b.setName(n);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ BRAND_TEXT_COLS.forEach(key -> applyStr(body, key, fieldSetter(b, key)));
|
|
|
|
|
+
|
|
|
|
|
+ Optional.ofNullable(body.get("sort_order"))
|
|
|
|
|
+ .filter(v -> v instanceof Number)
|
|
|
|
|
+ .ifPresent(v -> b.setSortOrder(((Number) v).intValue()));
|
|
|
|
|
+ Optional.ofNullable(body.get("is_active"))
|
|
|
|
|
+ .filter(v -> v instanceof Boolean)
|
|
|
|
|
+ .ifPresent(v -> b.setIsActive((Boolean) v));
|
|
|
|
|
+
|
|
|
brandRepository.save(b);
|
|
brandRepository.save(b);
|
|
|
- return Map.of("ok", true, "id", id, "message", "已更新");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "id", id, "message", "updated");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> deleteBrand(int id) {
|
|
public Map<String, Object> deleteBrand(int id) {
|
|
|
- if (!brandRepository.existsById(id)) throw new IllegalArgumentException("未找到该品牌");
|
|
|
|
|
|
|
+ brandRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("brand not found"));
|
|
|
brandRepository.deleteById(id);
|
|
brandRepository.deleteById(id);
|
|
|
- return Map.of("ok", true, "message", "已删除");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "message", "deleted");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> getBrand(int id) {
|
|
public Map<String, Object> getBrand(int id) {
|
|
|
- var opt = brandRepository.findById(id);
|
|
|
|
|
- if (opt.isEmpty()) throw new IllegalArgumentException("未找到该品牌");
|
|
|
|
|
- Brand b = opt.get();
|
|
|
|
|
|
|
+ Brand b = brandRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("brand not found"));
|
|
|
|
|
+
|
|
|
Map<String, Object> m = new LinkedHashMap<>();
|
|
Map<String, Object> m = new LinkedHashMap<>();
|
|
|
m.put("id", b.getId());
|
|
m.put("id", b.getId());
|
|
|
m.put("name", b.getName());
|
|
m.put("name", b.getName());
|
|
|
- m.put("function_indication", b.getFunctionIndication());
|
|
|
|
|
- m.put("usage_dosage", b.getUsageDosage());
|
|
|
|
|
- m.put("contraindication", b.getContraindication());
|
|
|
|
|
- m.put("ingredients", b.getIngredients());
|
|
|
|
|
- m.put("properties", b.getProperties());
|
|
|
|
|
- m.put("specification", b.getSpecification());
|
|
|
|
|
- m.put("adverse_reactions", b.getAdverseReactions());
|
|
|
|
|
- m.put("precautions", b.getPrecautions());
|
|
|
|
|
- m.put("execution_standard", b.getExecutionStandard());
|
|
|
|
|
- m.put("storage", b.getStorage());
|
|
|
|
|
- m.put("jump_url", b.getJumpUrl());
|
|
|
|
|
- m.put("description", b.getDescription());
|
|
|
|
|
- m.put("sort_order", b.getSortOrder());
|
|
|
|
|
|
|
+ BRAND_CSV_COLS.stream().filter(c -> !"name".equals(c))
|
|
|
|
|
+ .forEach(c -> m.put(c, nullToEmpty(brandField(b, c))));
|
|
|
m.put("is_active", b.getIsActive());
|
|
m.put("is_active", b.getIsActive());
|
|
|
m.put("created_at", b.getCreatedAt());
|
|
m.put("created_at", b.getCreatedAt());
|
|
|
m.put("updated_at", b.getUpdatedAt());
|
|
m.put("updated_at", b.getUpdatedAt());
|
|
|
return m;
|
|
return m;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== 匹配规则 CRUD ====================
|
|
|
|
|
|
|
+ // ==================== rule CRUD ====================
|
|
|
|
|
|
|
|
public Map<String, Object> listRules(String keyword, int page, int pageSize) {
|
|
public Map<String, Object> listRules(String keyword, int page, int pageSize) {
|
|
|
List<BrandRecommendRule> all = ruleRepository.findAllByOrderByTierAscKeywordAsc();
|
|
List<BrandRecommendRule> all = ruleRepository.findAllByOrderByTierAscKeywordAsc();
|
|
|
- if (keyword != null && !keyword.isBlank()) {
|
|
|
|
|
- all = all.stream()
|
|
|
|
|
- .filter(r -> r.getKeyword().contains(keyword))
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Optional.ofNullable(keyword)
|
|
|
|
|
+ .filter(k -> !k.isBlank())
|
|
|
|
|
+ .ifPresent(k -> all.removeIf(r -> !r.getKeyword().contains(k)));
|
|
|
|
|
|
|
|
- // 加载关联品牌名
|
|
|
|
|
Set<Integer> brandIds = all.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
Set<Integer> brandIds = all.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
|
Map<Integer, String> brandNames = brandRepository.findAllById(brandIds).stream()
|
|
Map<Integer, String> brandNames = brandRepository.findAllById(brandIds).stream()
|
|
|
.collect(Collectors.toMap(Brand::getId, Brand::getName));
|
|
.collect(Collectors.toMap(Brand::getId, Brand::getName));
|
|
|
|
|
|
|
|
- int total = all.size();
|
|
|
|
|
- int from = (page - 1) * pageSize;
|
|
|
|
|
- int to = Math.min(from + pageSize, total);
|
|
|
|
|
- List<BrandRecommendRule> pageItems = from < total ? all.subList(from, to) : List.of();
|
|
|
|
|
-
|
|
|
|
|
- List<Map<String, Object>> items = new ArrayList<>();
|
|
|
|
|
- for (BrandRecommendRule r : pageItems) {
|
|
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>(paginate(all, page, pageSize));
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ List<BrandRecommendRule> items = (List<BrandRecommendRule>) result.get("items");
|
|
|
|
|
+ List<Map<String, Object>> enriched = items.stream().map(r -> {
|
|
|
Map<String, Object> m = new LinkedHashMap<>();
|
|
Map<String, Object> m = new LinkedHashMap<>();
|
|
|
m.put("id", r.getId());
|
|
m.put("id", r.getId());
|
|
|
m.put("keyword", r.getKeyword());
|
|
m.put("keyword", r.getKeyword());
|
|
@@ -251,95 +222,83 @@ public class BrandRecommendService {
|
|
|
m.put("is_active", r.getIsActive());
|
|
m.put("is_active", r.getIsActive());
|
|
|
m.put("created_at", r.getCreatedAt());
|
|
m.put("created_at", r.getCreatedAt());
|
|
|
m.put("updated_at", r.getUpdatedAt());
|
|
m.put("updated_at", r.getUpdatedAt());
|
|
|
- items.add(m);
|
|
|
|
|
- }
|
|
|
|
|
- return Map.of(
|
|
|
|
|
- "items", (Object) items,
|
|
|
|
|
- "page", page,
|
|
|
|
|
- "page_size", pageSize,
|
|
|
|
|
- "total", total,
|
|
|
|
|
- "total_pages", (int) Math.ceil((double) total / pageSize)
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return m;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+ result.put("items", enriched);
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> createRule(String keyword, int brandId, int tier) {
|
|
public Map<String, Object> createRule(String keyword, int brandId, int tier) {
|
|
|
- if (keyword == null || keyword.isBlank()) {
|
|
|
|
|
- throw new IllegalArgumentException("关键词不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- if (!brandRepository.existsById(brandId)) {
|
|
|
|
|
- throw new IllegalArgumentException("品牌不存在");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Optional.ofNullable(keyword).filter(k -> !k.isBlank())
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("keyword required"));
|
|
|
|
|
+ Optional.of(brandId).filter(brandRepository::existsById)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("brand not found"));
|
|
|
if (ruleRepository.existsByKeywordAndIdNot(keyword.trim(), null)) {
|
|
if (ruleRepository.existsByKeywordAndIdNot(keyword.trim(), null)) {
|
|
|
- throw new IllegalArgumentException("该关键词已存在");
|
|
|
|
|
|
|
+ throw new IllegalArgumentException("keyword already exists");
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
BrandRecommendRule rule = BrandRecommendRule.builder()
|
|
BrandRecommendRule rule = BrandRecommendRule.builder()
|
|
|
- .keyword(keyword.trim())
|
|
|
|
|
- .brandId(brandId)
|
|
|
|
|
- .tier(tier)
|
|
|
|
|
- .isActive(true)
|
|
|
|
|
- .build();
|
|
|
|
|
|
|
+ .keyword(keyword.trim()).brandId(brandId).tier(tier).isActive(true).build();
|
|
|
rule = ruleRepository.save(rule);
|
|
rule = ruleRepository.save(rule);
|
|
|
- return Map.of("ok", true, "id", rule.getId(), "message", "已创建");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "id", rule.getId(), "message", "created");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> updateRule(int id, String keyword, Integer brandId, Integer tier, Boolean isActive) {
|
|
public Map<String, Object> updateRule(int id, String keyword, Integer brandId, Integer tier, Boolean isActive) {
|
|
|
- var opt = ruleRepository.findById(id);
|
|
|
|
|
- if (opt.isEmpty()) throw new IllegalArgumentException("未找到该规则");
|
|
|
|
|
- BrandRecommendRule rule = opt.get();
|
|
|
|
|
- if (keyword != null && !keyword.isBlank()) {
|
|
|
|
|
- if (ruleRepository.existsByKeywordAndIdNot(keyword.trim(), id)) {
|
|
|
|
|
- throw new IllegalArgumentException("该关键词已存在");
|
|
|
|
|
- }
|
|
|
|
|
- rule.setKeyword(keyword.trim());
|
|
|
|
|
- }
|
|
|
|
|
- if (brandId != null) {
|
|
|
|
|
- if (!brandRepository.existsById(brandId)) {
|
|
|
|
|
- throw new IllegalArgumentException("品牌不存在");
|
|
|
|
|
|
|
+ BrandRecommendRule rule = ruleRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("rule not found"));
|
|
|
|
|
+
|
|
|
|
|
+ Optional.ofNullable(keyword).filter(k -> !k.isBlank()).ifPresent(k -> {
|
|
|
|
|
+ if (ruleRepository.existsByKeywordAndIdNot(k.trim(), id)) {
|
|
|
|
|
+ throw new IllegalArgumentException("keyword already exists");
|
|
|
}
|
|
}
|
|
|
- rule.setBrandId(brandId);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ rule.setKeyword(k.trim());
|
|
|
|
|
+ });
|
|
|
|
|
+ Optional.ofNullable(brandId)
|
|
|
|
|
+ .filter(brandRepository::existsById)
|
|
|
|
|
+ .ifPresent(rule::setBrandId);
|
|
|
Optional.ofNullable(tier).ifPresent(rule::setTier);
|
|
Optional.ofNullable(tier).ifPresent(rule::setTier);
|
|
|
Optional.ofNullable(isActive).ifPresent(rule::setIsActive);
|
|
Optional.ofNullable(isActive).ifPresent(rule::setIsActive);
|
|
|
|
|
+
|
|
|
ruleRepository.save(rule);
|
|
ruleRepository.save(rule);
|
|
|
- return Map.of("ok", true, "id", id, "message", "已更新");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "id", id, "message", "updated");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> deleteRule(int id) {
|
|
public Map<String, Object> deleteRule(int id) {
|
|
|
- if (!ruleRepository.existsById(id)) throw new IllegalArgumentException("未找到该规则");
|
|
|
|
|
|
|
+ ruleRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("rule not found"));
|
|
|
ruleRepository.deleteById(id);
|
|
ruleRepository.deleteById(id);
|
|
|
- return Map.of("ok", true, "message", "已删除");
|
|
|
|
|
|
|
+ return Map.of("ok", true, "message", "deleted");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, Object> toggleRule(int id, Boolean isActive) {
|
|
public Map<String, Object> toggleRule(int id, Boolean isActive) {
|
|
|
- var opt = ruleRepository.findById(id);
|
|
|
|
|
- if (opt.isEmpty()) throw new IllegalArgumentException("未找到该规则");
|
|
|
|
|
- BrandRecommendRule rule = opt.get();
|
|
|
|
|
|
|
+ BrandRecommendRule rule = ruleRepository.findById(id)
|
|
|
|
|
+ .orElseThrow(() -> new IllegalArgumentException("rule not found"));
|
|
|
boolean newState = isActive != null ? isActive : !Boolean.TRUE.equals(rule.getIsActive());
|
|
boolean newState = isActive != null ? isActive : !Boolean.TRUE.equals(rule.getIsActive());
|
|
|
rule.setIsActive(newState);
|
|
rule.setIsActive(newState);
|
|
|
ruleRepository.save(rule);
|
|
ruleRepository.save(rule);
|
|
|
return Map.of("ok", true, "id", id, "is_active", newState,
|
|
return Map.of("ok", true, "id", id, "is_active", newState,
|
|
|
- "message", Boolean.TRUE.equals(newState) ? "已启用" : "已停用");
|
|
|
|
|
|
|
+ "message", newState ? "enabled" : "disabled");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== CSV 导入导出 ====================
|
|
|
|
|
|
|
+ // ==================== CSV import/export ====================
|
|
|
|
|
|
|
|
public Map<String, Object> importBrandsCsv(String csv) {
|
|
public Map<String, Object> importBrandsCsv(String csv) {
|
|
|
|
|
+ List<String> lines = csvLines(csv);
|
|
|
|
|
+ if (lines.isEmpty() || !lines.getFirst().equals(BRANDS_CSV_HEADER)) {
|
|
|
|
|
+ throw new IllegalArgumentException("CSV format incorrect: expected header " + BRANDS_CSV_HEADER);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
int success = 0, failed = 0;
|
|
int success = 0, failed = 0;
|
|
|
- for (String line : csv.split("\\n")) {
|
|
|
|
|
- line = line.trim();
|
|
|
|
|
- if (line.isEmpty() || line.startsWith("#")) continue;
|
|
|
|
|
- String[] cols = line.split(",", 14);
|
|
|
|
|
- if (cols.length < 1) { failed++; continue; }
|
|
|
|
|
|
|
+ for (int i = 1; i < lines.size(); i++) {
|
|
|
|
|
+ String[] cols = lines.get(i).split(",", BRAND_CSV_COLS.size());
|
|
|
try {
|
|
try {
|
|
|
String name = cols[0].trim();
|
|
String name = cols[0].trim();
|
|
|
- if (name.isEmpty() || brandRepository.existsByNameAndIdNot(name, null)) {
|
|
|
|
|
- failed++; continue;
|
|
|
|
|
- }
|
|
|
|
|
- Brand b = Brand.builder()
|
|
|
|
|
|
|
+ if (name.isEmpty() || brandRepository.existsByNameAndIdNot(name, null)) { failed++; continue; }
|
|
|
|
|
+ brandRepository.save(Brand.builder()
|
|
|
.name(name)
|
|
.name(name)
|
|
|
.functionIndication(col(cols, 1))
|
|
.functionIndication(col(cols, 1))
|
|
|
.usageDosage(col(cols, 2))
|
|
.usageDosage(col(cols, 2))
|
|
@@ -355,100 +314,193 @@ public class BrandRecommendService {
|
|
|
.description(col(cols, 12))
|
|
.description(col(cols, 12))
|
|
|
.sortOrder(cols.length > 13 ? Integer.parseInt(cols[13].trim()) : 0)
|
|
.sortOrder(cols.length > 13 ? Integer.parseInt(cols[13].trim()) : 0)
|
|
|
.isActive(true)
|
|
.isActive(true)
|
|
|
- .build();
|
|
|
|
|
- brandRepository.save(b);
|
|
|
|
|
|
|
+ .build());
|
|
|
success++;
|
|
success++;
|
|
|
} catch (Exception e) { failed++; }
|
|
} catch (Exception e) { failed++; }
|
|
|
}
|
|
}
|
|
|
return Map.of("ok", true, "success", success, "failed", failed);
|
|
return Map.of("ok", true, "success", success, "failed", failed);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public String exportBrandsCsv() {
|
|
|
|
|
- List<Brand> brands = brandRepository.findAllByOrderBySortOrderAsc();
|
|
|
|
|
- StringBuilder sb = new StringBuilder(
|
|
|
|
|
- "name,function_indication,usage_dosage,contraindication,ingredients,properties,specification,adverse_reactions,precautions,execution_standard,storage,jump_url,description,sort_order\\n");
|
|
|
|
|
- for (Brand b : brands) {
|
|
|
|
|
- sb.append(esc(b.getName())).append(",")
|
|
|
|
|
- .append(esc(b.getFunctionIndication())).append(",")
|
|
|
|
|
- .append(esc(b.getUsageDosage())).append(",")
|
|
|
|
|
- .append(esc(b.getContraindication())).append(",")
|
|
|
|
|
- .append(esc(b.getIngredients())).append(",")
|
|
|
|
|
- .append(esc(b.getProperties())).append(",")
|
|
|
|
|
- .append(esc(b.getSpecification())).append(",")
|
|
|
|
|
- .append(esc(b.getAdverseReactions())).append(",")
|
|
|
|
|
- .append(esc(b.getPrecautions())).append(",")
|
|
|
|
|
- .append(esc(b.getExecutionStandard())).append(",")
|
|
|
|
|
- .append(esc(b.getStorage())).append(",")
|
|
|
|
|
- .append(esc(b.getJumpUrl())).append(",")
|
|
|
|
|
- .append(esc(b.getDescription())).append(",")
|
|
|
|
|
- .append(b.getSortOrder()).append("\\n");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public String exportBrandsCsv(Set<Integer> ids) {
|
|
|
|
|
+ List<Brand> brands = ids.isEmpty() ? List.of() : brandRepository.findAllById(ids);
|
|
|
|
|
+ StringBuilder sb = new StringBuilder(BRANDS_CSV_HEADER).append("\n");
|
|
|
|
|
+ List<Function<Brand, String>> extractors = List.of(
|
|
|
|
|
+ b -> esc(b.getName()),
|
|
|
|
|
+ b -> esc(b.getFunctionIndication()),
|
|
|
|
|
+ b -> esc(b.getUsageDosage()),
|
|
|
|
|
+ b -> esc(b.getContraindication()),
|
|
|
|
|
+ b -> esc(b.getIngredients()),
|
|
|
|
|
+ b -> esc(b.getProperties()),
|
|
|
|
|
+ b -> esc(b.getSpecification()),
|
|
|
|
|
+ b -> esc(b.getAdverseReactions()),
|
|
|
|
|
+ b -> esc(b.getPrecautions()),
|
|
|
|
|
+ b -> esc(b.getExecutionStandard()),
|
|
|
|
|
+ b -> esc(b.getStorage()),
|
|
|
|
|
+ b -> esc(b.getJumpUrl()),
|
|
|
|
|
+ b -> esc(b.getDescription()),
|
|
|
|
|
+ b -> String.valueOf(b.getSortOrder())
|
|
|
|
|
+ );
|
|
|
|
|
+ brands.forEach(b -> sb.append(extractors.stream()
|
|
|
|
|
+ .map(f -> f.apply(b)).collect(Collectors.joining(","))).append("\n"));
|
|
|
return sb.toString();
|
|
return sb.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> importRulesCsv(String csv) {
|
|
public Map<String, Object> importRulesCsv(String csv) {
|
|
|
|
|
+ List<String> lines = csvLines(csv);
|
|
|
|
|
+ if (lines.isEmpty() || !lines.getFirst().equals(RULES_CSV_HEADER)) {
|
|
|
|
|
+ throw new IllegalArgumentException("CSV format incorrect: expected header " + RULES_CSV_HEADER);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
int success = 0, failed = 0;
|
|
int success = 0, failed = 0;
|
|
|
- for (String line : csv.split("\\n")) {
|
|
|
|
|
- line = line.trim();
|
|
|
|
|
- if (line.isEmpty() || line.startsWith("#")) continue;
|
|
|
|
|
- String[] cols = line.split(",", 3);
|
|
|
|
|
- if (cols.length < 2) { failed++; continue; }
|
|
|
|
|
|
|
+ for (int i = 1; i < lines.size(); i++) {
|
|
|
|
|
+ String[] cols = lines.get(i).split(",", 5);
|
|
|
try {
|
|
try {
|
|
|
String kw = cols[0].trim();
|
|
String kw = cols[0].trim();
|
|
|
- int brandId = Integer.parseInt(cols[1].trim());
|
|
|
|
|
- int tier = cols.length > 2 ? Integer.parseInt(cols[2].trim()) : 1;
|
|
|
|
|
- if (!brandRepository.existsById(brandId)) { failed++; continue; }
|
|
|
|
|
|
|
+ if (kw.isEmpty()) { failed++; continue; }
|
|
|
|
|
+
|
|
|
|
|
+ int brandId = cols.length > 1 ? parseIntOrDefault(cols[1], 0) : 0;
|
|
|
|
|
+ String brandName = cols.length > 2 ? cols[2].trim() : "";
|
|
|
|
|
+ Brand matched = brandId > 0 && brandRepository.existsById(brandId)
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : brandRepository.findByName(brandName);
|
|
|
|
|
+ int resolvedId = matched != null ? matched.getId() : brandId;
|
|
|
|
|
+ if (resolvedId <= 0 || !brandRepository.existsById(resolvedId)) { failed++; continue; }
|
|
|
|
|
+
|
|
|
|
|
+ int tier = cols.length > 3 ? parseIntOrDefault(cols[3], 1) : 1;
|
|
|
if (ruleRepository.existsByKeywordAndIdNot(kw, null)) { failed++; continue; }
|
|
if (ruleRepository.existsByKeywordAndIdNot(kw, null)) { failed++; continue; }
|
|
|
ruleRepository.save(BrandRecommendRule.builder()
|
|
ruleRepository.save(BrandRecommendRule.builder()
|
|
|
- .keyword(kw).brandId(brandId).tier(tier).isActive(true).build());
|
|
|
|
|
|
|
+ .keyword(kw).brandId(resolvedId).tier(tier).isActive(true).build());
|
|
|
success++;
|
|
success++;
|
|
|
} catch (Exception e) { failed++; }
|
|
} catch (Exception e) { failed++; }
|
|
|
}
|
|
}
|
|
|
return Map.of("ok", true, "success", success, "failed", failed);
|
|
return Map.of("ok", true, "success", success, "failed", failed);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public String exportRulesCsv() {
|
|
|
|
|
- List<BrandRecommendRule> rules = ruleRepository.findAllByOrderByTierAscKeywordAsc();
|
|
|
|
|
|
|
+ public String exportRulesCsv(Set<Integer> ids) {
|
|
|
|
|
+ List<BrandRecommendRule> rules = ids.isEmpty() ? List.of() : ruleRepository.findAllById(ids);
|
|
|
Set<Integer> brandIds = rules.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
Set<Integer> brandIds = rules.stream().map(BrandRecommendRule::getBrandId).collect(Collectors.toSet());
|
|
|
Map<Integer, String> brandNames = brandRepository.findAllById(brandIds).stream()
|
|
Map<Integer, String> brandNames = brandRepository.findAllById(brandIds).stream()
|
|
|
.collect(Collectors.toMap(Brand::getId, Brand::getName));
|
|
.collect(Collectors.toMap(Brand::getId, Brand::getName));
|
|
|
- StringBuilder sb = new StringBuilder("keyword,brand_id,brand_name,tier,is_active\\n");
|
|
|
|
|
- for (BrandRecommendRule r : rules) {
|
|
|
|
|
- sb.append(r.getKeyword()).append(",")
|
|
|
|
|
- .append(r.getBrandId()).append(",")
|
|
|
|
|
- .append(brandNames.getOrDefault(r.getBrandId(), "")).append(",")
|
|
|
|
|
- .append(r.getTier()).append(",")
|
|
|
|
|
- .append(r.getIsActive()).append("\\n");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ StringBuilder sb = new StringBuilder(RULES_CSV_HEADER).append("\n");
|
|
|
|
|
+ rules.forEach(r -> sb.append(r.getKeyword()).append(",")
|
|
|
|
|
+ .append(r.getBrandId()).append(",")
|
|
|
|
|
+ .append(brandNames.getOrDefault(r.getBrandId(), "")).append(",")
|
|
|
|
|
+ .append(r.getTier()).append(",")
|
|
|
|
|
+ .append(r.getIsActive()).append("\n"));
|
|
|
return sb.toString();
|
|
return sb.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ==================== Helpers ====================
|
|
|
|
|
|
|
+ public byte[] exportBrandsCsvBytes(Set<Integer> ids) {
|
|
|
|
|
+ return exportBrandsCsv(ids).getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public byte[] exportRulesCsvBytes(Set<Integer> ids) {
|
|
|
|
|
+ return exportRulesCsv(ids).getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- private static String str(Map<String, Object> body, String key) {
|
|
|
|
|
- Object v = body.get(key);
|
|
|
|
|
- if (v == null) return null;
|
|
|
|
|
- String s = v.toString().trim();
|
|
|
|
|
- return s.isEmpty() ? null : s;
|
|
|
|
|
|
|
+ // ==================== helpers ====================
|
|
|
|
|
+
|
|
|
|
|
+ private static <T> Map<String, Object> paginate(List<T> all, int page, int pageSize) {
|
|
|
|
|
+ int total = all.size();
|
|
|
|
|
+ int from = (page - 1) * pageSize;
|
|
|
|
|
+ int to = Math.min(from + pageSize, total);
|
|
|
|
|
+ List<T> items = from < total ? all.subList(from, to) : List.of();
|
|
|
|
|
+ return Map.of(
|
|
|
|
|
+ "items", items,
|
|
|
|
|
+ "page", page,
|
|
|
|
|
+ "page_size", pageSize,
|
|
|
|
|
+ "total", total,
|
|
|
|
|
+ "total_pages", (int) Math.ceil((double) total / pageSize)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static List<String> csvLines(String csv) {
|
|
|
|
|
+ return Arrays.stream(csv.replace("\r\n", "\n").replace("\r", "\n").split("\n"))
|
|
|
|
|
+ .map(String::trim)
|
|
|
|
|
+ .filter(s -> !s.isEmpty() && !s.startsWith("#"))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String require(Map<String, Object> body) {
|
|
|
|
|
+ String v = str(body, "name");
|
|
|
|
|
+ if (v == null || v.isBlank()) {
|
|
|
|
|
+ throw new IllegalArgumentException("brand name required");
|
|
|
|
|
+ }
|
|
|
|
|
+ return v;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int parseIntOrDefault(String s, int def) {
|
|
|
|
|
+ try { return Integer.parseInt(s.trim()); } catch (NumberFormatException e) { return def; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Consumer<String> fieldSetter(Brand b, String col) {
|
|
|
|
|
+ return switch (col) {
|
|
|
|
|
+ case "function_indication" -> b::setFunctionIndication;
|
|
|
|
|
+ case "usage_dosage" -> b::setUsageDosage;
|
|
|
|
|
+ case "contraindication" -> b::setContraindication;
|
|
|
|
|
+ case "ingredients" -> b::setIngredients;
|
|
|
|
|
+ case "properties" -> b::setProperties;
|
|
|
|
|
+ case "specification" -> b::setSpecification;
|
|
|
|
|
+ case "adverse_reactions" -> b::setAdverseReactions;
|
|
|
|
|
+ case "precautions" -> b::setPrecautions;
|
|
|
|
|
+ case "execution_standard" -> b::setExecutionStandard;
|
|
|
|
|
+ case "storage" -> b::setStorage;
|
|
|
|
|
+ case "jump_url" -> b::setJumpUrl;
|
|
|
|
|
+ case "description" -> b::setDescription;
|
|
|
|
|
+ default -> v -> {};
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String brandField(Brand b, String col) {
|
|
|
|
|
+ return switch (col) {
|
|
|
|
|
+ case "function_indication" -> b.getFunctionIndication();
|
|
|
|
|
+ case "usage_dosage" -> b.getUsageDosage();
|
|
|
|
|
+ case "contraindication" -> b.getContraindication();
|
|
|
|
|
+ case "ingredients" -> b.getIngredients();
|
|
|
|
|
+ case "properties" -> b.getProperties();
|
|
|
|
|
+ case "specification" -> b.getSpecification();
|
|
|
|
|
+ case "adverse_reactions" -> b.getAdverseReactions();
|
|
|
|
|
+ case "precautions" -> b.getPrecautions();
|
|
|
|
|
+ case "execution_standard" -> b.getExecutionStandard();
|
|
|
|
|
+ case "storage" -> b.getStorage();
|
|
|
|
|
+ case "jump_url" -> b.getJumpUrl();
|
|
|
|
|
+ case "description" -> b.getDescription();
|
|
|
|
|
+ case "sort_order" -> String.valueOf(b.getSortOrder());
|
|
|
|
|
+ default -> "";
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static String nullToEmpty(String s) {
|
|
|
|
|
- return s != null ? s : "";
|
|
|
|
|
|
|
+ private static String str(Map<String, Object> body, String key) {
|
|
|
|
|
+ return Optional.ofNullable(body.get(key))
|
|
|
|
|
+ .map(Object::toString).map(String::trim)
|
|
|
|
|
+ .filter(s -> !s.isEmpty())
|
|
|
|
|
+ .orElse(null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** map 中存在 key 时应用字符串值 */
|
|
|
|
|
|
|
+ private static String nullToEmpty(String s) { return s != null ? s : ""; }
|
|
|
|
|
+
|
|
|
private static void applyStr(Map<String, Object> body, String key, Consumer<String> setter) {
|
|
private static void applyStr(Map<String, Object> body, String key, Consumer<String> setter) {
|
|
|
- Optional.ofNullable(str(body, key)).ifPresent(setter);
|
|
|
|
|
|
|
+ Optional.ofNullable(body.get(key))
|
|
|
|
|
+ .ifPresent(v -> {
|
|
|
|
|
+ String s = v.toString().trim();
|
|
|
|
|
+ if (!s.isEmpty()) {
|
|
|
|
|
+ setter.accept(s);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static String col(String[] cols, int idx) {
|
|
private static String col(String[] cols, int idx) {
|
|
|
- if (idx >= cols.length) return null;
|
|
|
|
|
- String s = cols[idx].trim();
|
|
|
|
|
- return s.isEmpty() ? null : s;
|
|
|
|
|
|
|
+ return idx < cols.length ? Optional.of(cols[idx].trim()).filter(s -> !s.isEmpty()).orElse(null) : null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static String esc(String s) {
|
|
private static String esc(String s) {
|
|
|
- if (s == null || s.isEmpty()) return "";
|
|
|
|
|
- return "\"" + s.replace("\"", "\"\"") + "\"";
|
|
|
|
|
|
|
+ return (s == null || s.isEmpty()) ? "" : "\"" + s.replace("\"", "\"\"") + "\"";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static int intVal(Map<String, Object> body) {
|
|
|
|
|
+ return Optional.ofNullable(body.get("sort_order"))
|
|
|
|
|
+ .filter(v -> v instanceof Number)
|
|
|
|
|
+ .map(v -> ((Number) v).intValue())
|
|
|
|
|
+ .orElse(0);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|