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.business.merchant.MerchantTypeCatalog;
|
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<Map<String, Object>> 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<String> aliases = aliasesOrNull(cardLevel);
|
List<Map<String, Object>> records = merchantMapper.selectAdminPage(
|
merchantId,
|
clean(keyword),
|
merchantTypeCode(merchantType),
|
aliases,
|
status,
|
safeSize,
|
(safePage - 1) * safeSize
|
);
|
Map<Long, List<Map<String, Object>>> benefits = benefits(records, aliases);
|
List<Map<String, Object>> views = records.stream()
|
.map(row -> adminView(row, benefits.getOrDefault(longValue(row, "merchantId"), List.of())))
|
.toList();
|
Long total = merchantMapper.countAdminPage(merchantId, clean(keyword), merchantTypeCode(merchantType), aliases, status);
|
return new PageResult<>(views, safePage, safeSize, total == null ? 0 : total);
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long merchantId) {
|
Long id = requireMerchantId(merchantId);
|
Map<String, Object> row = merchantMapper.selectAdminDetail(id);
|
if (row == null || row.isEmpty()) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "商户不存在");
|
}
|
List<Map<String, Object>> benefits = benefitMapper.selectList(new QueryWrapper<MerchantCardBenefit>()
|
.eq("merchant_id", id)
|
.orderByDesc("sort_no")
|
.orderByDesc("id"))
|
.stream()
|
.map(this::benefitView)
|
.toList();
|
return adminView(row, benefits);
|
}
|
|
@Transactional
|
public Map<String, Object> save(Long merchantId, MerchantDisplayPolicySaveRequest request) {
|
Long id = requireMerchantId(merchantId);
|
MerchantDisplayPolicyMerchant merchant = merchantMapper.selectOne(new QueryWrapper<MerchantDisplayPolicyMerchant>()
|
.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<String, Object> updateStatus(Long merchantId, Integer status) {
|
Long id = requireMerchantId(merchantId);
|
validateStatus(status, "上下架状态只能为1上架或0下架");
|
int updated = merchantMapper.update(null, new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.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<Map<String, Object>> 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<String> aliases = aliasesOrNull(cardLevel);
|
List<Map<String, Object>> 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<MerchantDisplayPolicyMerchant> update = new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.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 String merchantTypeCode(String value) {
|
String cleaned = clean(value);
|
return cleaned.isBlank() ? "" : MerchantTypeCatalog.canonicalCode(cleaned);
|
}
|
|
private void upsertPolicies(Long merchantId, List<MerchantDisplayPolicySaveRequest.CardPolicy> policies) {
|
if (policies == null) {
|
return;
|
}
|
if (policies.stream().anyMatch(Objects::isNull)) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡类型政策不能为空");
|
}
|
Set<String> 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<MerchantCardBenefit>()
|
.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<MerchantCardBenefit>()
|
.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<Long, List<Map<String, Object>>> benefits(List<Map<String, Object>> records, List<String> aliases) {
|
List<Long> merchantIds = records.stream()
|
.map(row -> longValue(row, "merchantId"))
|
.filter(Objects::nonNull)
|
.toList();
|
if (merchantIds.isEmpty()) {
|
return Map.of();
|
}
|
QueryWrapper<MerchantCardBenefit> query = new QueryWrapper<MerchantCardBenefit>()
|
.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<String, Object> adminView(Map<String, Object> row, List<Map<String, Object>> benefits) {
|
Map<String, Object> 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<String, Object> benefitView(MerchantCardBenefit benefit) {
|
Map<String, Object> 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<String, Object> eventView(Map<String, Object> row) {
|
Map<String, Object> 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<String> aliasesOrNull(String cardLevel) {
|
String normalized = normalizeCardLevel(cardLevel);
|
return normalized.isBlank() ? null : cardLevelAliases(normalized);
|
}
|
|
private List<String> 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<String, Object> row, String key) {
|
return row == null ? null : row.get(key);
|
}
|
|
private String text(Map<String, Object> row, String key) {
|
Object value = value(row, key);
|
return value == null ? "" : String.valueOf(value).trim();
|
}
|
|
private Long longValue(Map<String, Object> 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();
|
}
|
}
|