package com.talentcard.server.service; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.talentcard.server.business.PageResult; import com.talentcard.server.common.BusinessException; import com.talentcard.server.common.ErrorCode; import com.talentcard.server.entity.MerchantCardBenefit; import com.talentcard.server.entity.MerchantDisplayPolicyMerchant; import com.talentcard.server.mapper.MerchantCardBenefitMapper; import com.talentcard.server.mapper.MerchantDisplayPolicyMerchantMapper; import com.talentcard.server.request.MerchantDisplayPolicySaveRequest; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MerchantDisplayPolicyAdminService { private final MerchantDisplayPolicyMerchantMapper merchantMapper; private final MerchantCardBenefitMapper benefitMapper; public MerchantDisplayPolicyAdminService( MerchantDisplayPolicyMerchantMapper merchantMapper, MerchantCardBenefitMapper benefitMapper ) { this.merchantMapper = merchantMapper; this.benefitMapper = benefitMapper; } @Transactional(readOnly = true) public PageResult> page( Long merchantId, String keyword, String merchantType, String cardLevel, Integer status, int page, int size ) { validateStatus(status, "上下架状态只能为1上架或0下架"); int safePage = Math.max(page, 1); int safeSize = Math.min(Math.max(size, 1), 100); List aliases = aliasesOrNull(cardLevel); List> records = merchantMapper.selectAdminPage( merchantId, clean(keyword), clean(merchantType), aliases, status, safeSize, (safePage - 1) * safeSize ); Map>> benefits = benefits(records, aliases); List> views = records.stream() .map(row -> adminView(row, benefits.getOrDefault(longValue(row, "merchantId"), List.of()))) .toList(); Long total = merchantMapper.countAdminPage(merchantId, clean(keyword), clean(merchantType), aliases, status); return new PageResult<>(views, safePage, safeSize, total == null ? 0 : total); } @Transactional(readOnly = true) public Map detail(Long merchantId) { Long id = requireMerchantId(merchantId); Map row = merchantMapper.selectAdminDetail(id); if (row == null || row.isEmpty()) { throw new BusinessException(ErrorCode.NOT_FOUND, "商户不存在"); } List> benefits = benefitMapper.selectList(new QueryWrapper() .eq("merchant_id", id) .orderByDesc("sort_no") .orderByDesc("id")) .stream() .map(this::benefitView) .toList(); return adminView(row, benefits); } @Transactional public Map save(Long merchantId, MerchantDisplayPolicySaveRequest request) { Long id = requireMerchantId(merchantId); MerchantDisplayPolicyMerchant merchant = merchantMapper.selectOne(new QueryWrapper() .eq("id", id) .eq("deleted", 0) .last("limit 1")); if (merchant == null) { throw new BusinessException(ErrorCode.NOT_FOUND, "商户不存在"); } updateMerchant(id, request); upsertPolicies(id, request.cardPolicies()); return detail(id); } @Transactional public Map updateStatus(Long merchantId, Integer status) { Long id = requireMerchantId(merchantId); validateStatus(status, "上下架状态只能为1上架或0下架"); int updated = merchantMapper.update(null, new UpdateWrapper() .set("status", status) .eq("id", id) .eq("deleted", 0)); if (updated != 1) { throw new BusinessException(ErrorCode.NOT_FOUND, "商户不存在"); } return detail(id); } @Transactional(readOnly = true) public PageResult> events( Long merchantId, String eventType, String cardLevel, String keyword, int page, int size ) { int safePage = Math.max(page, 1); int safeSize = Math.min(Math.max(size, 1), 100); String normalizedEventType = normalizeEventType(eventType, false); List aliases = aliasesOrNull(cardLevel); List> records = merchantMapper.selectEventPage( merchantId, normalizedEventType, aliases, clean(keyword), safeSize, (safePage - 1) * safeSize ).stream().map(this::eventView).toList(); Long total = merchantMapper.countEventPage(merchantId, normalizedEventType, aliases, clean(keyword)); return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total); } private void updateMerchant(Long merchantId, MerchantDisplayPolicySaveRequest request) { UpdateWrapper update = new UpdateWrapper() .eq("id", merchantId) .eq("deleted", 0); boolean hasUpdate = false; if (request.contactName() != null) { update.set("contact_name", clean(request.contactName())); hasUpdate = true; } if (request.contactMobile() != null) { update.set("contact_mobile", clean(request.contactMobile())); hasUpdate = true; } if (request.rightsText() != null) { update.set("rights_text", clean(request.rightsText())); hasUpdate = true; } if (request.handlingInstructions() != null) { update.set("usage_instructions", clean(request.handlingInstructions())); hasUpdate = true; } if (request.sortNo() != null) { update.set("sort_no", request.sortNo()); hasUpdate = true; } if (request.status() != null) { validateStatus(request.status(), "上下架状态只能为1上架或0下架"); update.set("status", request.status()); hasUpdate = true; } if (hasUpdate) { update.set("updated_at", LocalDateTime.now()); int updated = merchantMapper.update(null, update); if (updated != 1) { throw new BusinessException(ErrorCode.CONFLICT, "商户信息已变更,请刷新后重试"); } } } private void upsertPolicies(Long merchantId, List policies) { if (policies == null) { return; } if (policies.stream().anyMatch(Objects::isNull)) { throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡类型政策不能为空"); } Set levels = policies.stream() .map(policy -> normalizeCardLevel(policy.cardLevel())) .filter(level -> !level.isBlank()) .collect(Collectors.toSet()); if (levels.size() != policies.size()) { throw new BusinessException(ErrorCode.BAD_REQUEST, "同一次保存不能包含重复或空的人才卡类型"); } for (MerchantDisplayPolicySaveRequest.CardPolicy policy : policies) { savePolicy(merchantId, policy); } } private void savePolicy(Long merchantId, MerchantDisplayPolicySaveRequest.CardPolicy request) { String cardLevel = normalizeCardLevel(request.cardLevel()); if (cardLevel.isBlank()) { throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡类型不能为空"); } String benefitText = clean(request.benefitText()); if (benefitText.isBlank()) { throw new BusinessException(ErrorCode.BAD_REQUEST, "优惠政策内容不能为空"); } int status = request.status() == null ? 1 : request.status(); validateStatus(status, "政策状态只能为1启用或0停用"); MerchantCardBenefit current = benefitMapper.selectOne(new QueryWrapper() .eq("merchant_id", merchantId) .eq("card_level", cardLevel) .last("limit 1")); if (current == null) { MerchantCardBenefit benefit = new MerchantCardBenefit(); benefit.setMerchantId(merchantId); benefit.setCardLevel(cardLevel); benefit.setCardName(firstNonBlank(request.cardName(), defaultCardName(cardLevel))); benefit.setBenefitText(benefitText); benefit.setTheme(firstNonBlank(request.theme(), defaultTheme(cardLevel))); benefit.setSortNo(request.sortNo() == null ? defaultSortNo(cardLevel) : request.sortNo()); benefit.setStatus(status); try { benefitMapper.insert(benefit); } catch (DuplicateKeyException exception) { throw new BusinessException(ErrorCode.CONFLICT, "该商户的人才卡类型政策已存在,请刷新后重试"); } return; } int updated = benefitMapper.update(null, new UpdateWrapper() .set("card_name", firstNonBlank(request.cardName(), defaultCardName(cardLevel))) .set("benefit_text", benefitText) .set("theme", firstNonBlank(request.theme(), defaultTheme(cardLevel))) .set("sort_no", request.sortNo() == null ? current.getSortNo() : request.sortNo()) .set("status", status) .set("updated_at", LocalDateTime.now()) .eq("id", current.getId())); if (updated != 1) { throw new BusinessException(ErrorCode.CONFLICT, "政策配置已变更,请刷新后重试"); } } private Map>> benefits(List> records, List aliases) { List merchantIds = records.stream() .map(row -> longValue(row, "merchantId")) .filter(Objects::nonNull) .toList(); if (merchantIds.isEmpty()) { return Map.of(); } QueryWrapper query = new QueryWrapper() .in("merchant_id", merchantIds) .orderByDesc("sort_no") .orderByDesc("id"); if (aliases != null && !aliases.isEmpty()) { query.in("card_level", aliases); } return benefitMapper.selectList(query).stream() .map(this::benefitView) .collect(Collectors.groupingBy(row -> longValue(row, "merchantId"), LinkedHashMap::new, Collectors.toList())); } private Map adminView(Map row, List> benefits) { Map result = new LinkedHashMap<>(); result.put("merchantId", value(row, "merchantId")); result.put("merchantName", text(row, "merchantName")); result.put("merchantType", text(row, "merchantType")); result.put("contactName", text(row, "contactName")); result.put("contactMobile", text(row, "contactMobile")); result.put("logoUrl", text(row, "logoUrl")); result.put("coverUrl", text(row, "coverUrl")); result.put("address", text(row, "address")); result.put("businessStatus", text(row, "businessStatus")); result.put("longitude", value(row, "longitude")); result.put("latitude", value(row, "latitude")); result.put("rightsText", text(row, "rightsText")); result.put("handlingInstructions", text(row, "handlingInstructions")); result.put("serviceLabel", text(row, "serviceLabel")); result.put("recommend", intValue(value(row, "recommend"))); result.put("sortNo", intValue(value(row, "sortNo"))); result.put("status", intValue(value(row, "status"))); result.put("statusName", intValue(value(row, "status")) == 1 ? "上架" : "下架"); result.put("policyCount", intValue(value(row, "policyCount"))); result.put("cardPolicies", benefits); result.put("createdAt", value(row, "createdAt")); result.put("updatedAt", value(row, "updatedAt")); return result; } private Map benefitView(MerchantCardBenefit benefit) { Map result = new LinkedHashMap<>(); result.put("id", benefit.getId()); result.put("merchantId", benefit.getMerchantId()); result.put("cardLevel", benefit.getCardLevel()); result.put("cardName", benefit.getCardName()); result.put("benefitText", benefit.getBenefitText()); result.put("policyText", benefit.getBenefitText()); result.put("theme", benefit.getTheme()); result.put("sortNo", benefit.getSortNo()); result.put("status", benefit.getStatus()); result.put("statusName", intValue(benefit.getStatus()) == 1 ? "启用" : "停用"); result.put("createdAt", benefit.getCreatedAt()); result.put("updatedAt", benefit.getUpdatedAt()); return result; } private Map eventView(Map row) { Map result = new LinkedHashMap<>(row); String eventType = text(row, "eventType"); result.put("eventTypeName", "CONSULT".equals(eventType) ? "咨询" : "浏览"); return result; } private String normalizeEventType(String value, boolean required) { String raw = clean(value); if (raw.isBlank()) { if (required) { throw new BusinessException(ErrorCode.BAD_REQUEST, "事件类型不能为空"); } return ""; } String upper = raw.toUpperCase(Locale.ROOT); if ("VIEW".equals(upper) || "浏览".equals(raw)) { return "VIEW"; } if ("CONSULT".equals(upper) || "咨询".equals(raw)) { return "CONSULT"; } throw new BusinessException(ErrorCode.BAD_REQUEST, "事件类型只能为VIEW浏览或CONSULT咨询"); } private Long requireMerchantId(Long merchantId) { if (merchantId == null || merchantId <= 0) { throw new BusinessException(ErrorCode.BAD_REQUEST, "商户ID不能为空"); } return merchantId; } private void validateStatus(Integer status, String message) { if (status != null && status != 0 && status != 1) { throw new BusinessException(ErrorCode.BAD_REQUEST, message); } } private List aliasesOrNull(String cardLevel) { String normalized = normalizeCardLevel(cardLevel); return normalized.isBlank() ? null : cardLevelAliases(normalized); } private List cardLevelAliases(String cardLevel) { String normalized = normalizeCardLevel(cardLevel); if ("EXCELLENT".equals(normalized)) { return List.of("EXCELLENT", "卓越人才卡", "卓越"); } if ("OUTSTANDING".equals(normalized)) { return List.of("OUTSTANDING", "杰出人才卡", "杰出"); } if ("ELITE".equals(normalized)) { return List.of("ELITE", "菁英人才卡", "菁英"); } return List.of(normalized); } private String normalizeCardLevel(String value) { String raw = clean(value); String upper = raw.toUpperCase(Locale.ROOT); if (upper.contains("EXCELLENT") || raw.contains("卓越")) { return "EXCELLENT"; } if (upper.contains("OUTSTANDING") || raw.contains("杰出")) { return "OUTSTANDING"; } if (upper.contains("ELITE") || raw.contains("菁英")) { return "ELITE"; } return upper; } private String defaultCardName(String cardLevel) { return switch (cardLevel) { case "EXCELLENT" -> "卓越人才卡"; case "OUTSTANDING" -> "杰出人才卡"; case "ELITE" -> "菁英人才卡"; default -> cardLevel; }; } private String defaultTheme(String cardLevel) { return switch (cardLevel) { case "EXCELLENT" -> "excellent"; case "OUTSTANDING" -> "outstanding"; case "ELITE" -> "elite"; default -> "default"; }; } private int defaultSortNo(String cardLevel) { return switch (cardLevel) { case "EXCELLENT" -> 30; case "OUTSTANDING" -> 20; case "ELITE" -> 10; default -> 0; }; } private Object value(Map row, String key) { return row == null ? null : row.get(key); } private String text(Map row, String key) { Object value = value(row, key); return value == null ? "" : String.valueOf(value).trim(); } private Long longValue(Map row, String key) { Object value = value(row, key); return value instanceof Number number ? number.longValue() : null; } private int intValue(Object value) { return value instanceof Number number ? number.intValue() : 0; } private String firstNonBlank(String first, String fallback) { String value = clean(first); return value.isBlank() ? fallback : value; } private String clean(String value) { return value == null ? "" : value.trim(); } }