package com.talentcard.server.service;
|
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
|
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.entity.ShoppingTopicMerchant;
|
import com.talentcard.server.mapper.DisplayServiceMapper;
|
import com.talentcard.server.mapper.MerchantCardBenefitMapper;
|
import com.talentcard.server.mapper.ShoppingTopicMerchantMapper;
|
import com.talentcard.server.request.DisplayServiceSaveRequest;
|
import com.talentcard.server.request.DisplayServiceSortBatchRequest;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class DisplayServiceAdminService {
|
|
private static final List<Category> CATEGORIES = List.of(
|
new Category("parking", "停车"),
|
new Category("medical", "体检"),
|
new Category("housing", "购房"),
|
new Category("car", "购车"),
|
new Category("appliance", "家电"),
|
new Category("apartment", "公寓租赁")
|
);
|
private static final Set<String> CATEGORY_KEYS = Set.of("parking", "medical", "housing", "car", "appliance", "apartment");
|
private static final Set<String> SHOPPING_CATEGORY_KEYS = Set.of("housing", "car", "appliance");
|
private static final Map<String, String> CATEGORY_MERCHANT_TYPES = Map.of(
|
"parking", "parking",
|
"medical", "medical",
|
"apartment", "apartment",
|
"housing", "shopping",
|
"car", "shopping",
|
"appliance", "shopping"
|
);
|
|
private final DisplayServiceMapper serviceMapper;
|
private final MerchantCardBenefitMapper benefitMapper;
|
private final ShoppingTopicMerchantMapper shoppingMapper;
|
|
public DisplayServiceAdminService(
|
DisplayServiceMapper serviceMapper,
|
MerchantCardBenefitMapper benefitMapper,
|
ShoppingTopicMerchantMapper shoppingMapper
|
) {
|
this.serviceMapper = serviceMapper;
|
this.benefitMapper = benefitMapper;
|
this.shoppingMapper = shoppingMapper;
|
}
|
|
@Transactional(readOnly = true)
|
public List<Map<String, Object>> categories() {
|
return CATEGORIES.stream().map(category -> {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("key", category.key());
|
result.put("name", category.name());
|
return result;
|
}).toList();
|
}
|
|
@Transactional(readOnly = true)
|
public PageResult<Map<String, Object>> page(String category, String keyword, Integer status, int page, int size) {
|
validateCategoryFilter(category);
|
validateStatus(status, "上下架状态只能为1上架或0下架");
|
int safePage = Math.max(page, 1);
|
int safeSize = Math.min(Math.max(size, 1), 100);
|
List<Map<String, Object>> records = serviceMapper.selectAdminPage(
|
clean(category),
|
clean(keyword),
|
status,
|
safeSize,
|
(safePage - 1) * safeSize
|
).stream()
|
.map(this::view)
|
.toList();
|
Long total = serviceMapper.countAdminPage(clean(category), clean(keyword), status);
|
return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total);
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long serviceId) {
|
Long id = requireId(serviceId, "展示服务ID不能为空");
|
Map<String, Object> row = serviceMapper.selectAdminDetail(id);
|
if (row == null || row.isEmpty()) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "展示服务不存在");
|
}
|
List<Map<String, Object>> policies = benefitMapper.selectList(new QueryWrapper<MerchantCardBenefit>()
|
.eq("merchant_id", id)
|
.orderByDesc("sort_no")
|
.orderByDesc("id"))
|
.stream()
|
.map(this::policyView)
|
.toList();
|
Map<String, Object> result = view(row);
|
result.put("cardPolicies", policies);
|
return result;
|
}
|
|
@Transactional
|
public Map<String, Object> create(DisplayServiceSaveRequest request) {
|
String category = validateCategory(request.category());
|
String merchantType = CATEGORY_MERCHANT_TYPES.get(category);
|
MerchantDisplayPolicyMerchant merchant = new MerchantDisplayPolicyMerchant();
|
applyMerchant(merchant, request, merchantType);
|
merchant.setDeleted(0);
|
serviceMapper.insert(merchant);
|
if (SHOPPING_CATEGORY_KEYS.contains(category)) {
|
upsertShoppingMapping(merchant.getId(), category, merchant.getSortNo());
|
}
|
upsertCardPolicies(merchant.getId(), request.cardPolicies());
|
return detail(merchant.getId());
|
}
|
|
@Transactional
|
public Map<String, Object> update(Long serviceId, DisplayServiceSaveRequest request) {
|
Long id = requireId(serviceId, "展示服务ID不能为空");
|
requireMerchant(id);
|
String category = validateCategory(request.category());
|
String merchantType = CATEGORY_MERCHANT_TYPES.get(category);
|
int sortNo = request.sortNo() == null ? 0 : request.sortNo();
|
int status = request.status() == null ? 1 : request.status();
|
validateStatus(status, "上下架状态只能为1上架或0下架");
|
int updated = serviceMapper.update(null, new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.set("merchant_name", cleanRequired(request.merchantName(), "展示服务名称不能为空"))
|
.set("merchant_type", merchantType)
|
.set("contact_name", clean(request.contactName()))
|
.set("contact_mobile", clean(request.contactMobile()))
|
.set("logo_url", clean(request.logoUrl()))
|
.set("cover_url", clean(request.coverUrl()))
|
.set("address", clean(request.address()))
|
.set("service_label", clean(request.serviceLabel()))
|
.set("rights_text", clean(request.rightsText()))
|
.set("usage_instructions", clean(request.handlingInstructions()))
|
.set("sort_no", sortNo)
|
.set("status", status)
|
.set("recommend", request.recommend() == null ? 0 : request.recommend())
|
.eq("id", id)
|
.eq("deleted", 0));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "展示服务信息已变更,请刷新后重试");
|
}
|
if (SHOPPING_CATEGORY_KEYS.contains(category)) {
|
upsertShoppingMapping(id, category, sortNo);
|
} else {
|
removeShoppingMapping(id);
|
}
|
upsertCardPolicies(id, request.cardPolicies());
|
return detail(id);
|
}
|
|
@Transactional
|
public Map<String, Object> updateStatus(Long serviceId, Integer status) {
|
Long id = requireId(serviceId, "展示服务ID不能为空");
|
requireMerchant(id);
|
validateStatus(status, "上下架状态只能为1上架或0下架");
|
int updated = serviceMapper.update(null, new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.set("status", status)
|
.eq("id", id)
|
.eq("deleted", 0));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "展示服务状态已变更,请刷新后重试");
|
}
|
return detail(id);
|
}
|
|
@Transactional
|
public Map<String, Object> updateSort(Long serviceId, Integer sortNo) {
|
Long id = requireId(serviceId, "展示服务ID不能为空");
|
requireMerchant(id);
|
int sort = sortNo == null ? 0 : sortNo;
|
int updated = serviceMapper.update(null, new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.set("sort_no", sort)
|
.eq("id", id)
|
.eq("deleted", 0));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "展示服务状态已变更,请刷新后重试");
|
}
|
shoppingMapper.update(null, new UpdateWrapper<ShoppingTopicMerchant>()
|
.set("sort_no", sort)
|
.eq("merchant_id", id)
|
.eq("deleted", 0));
|
return detail(id);
|
}
|
|
@Transactional
|
public Map<String, Object> updateSortBatch(DisplayServiceSortBatchRequest request) {
|
if (request.items() == null || request.items().isEmpty()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "排序项列表不能为空");
|
}
|
int updatedCount = 0;
|
for (DisplayServiceSortBatchRequest.SortItem item : request.items()) {
|
Long id = requireId(item.serviceId(), "展示服务ID不能为空");
|
requireMerchant(id);
|
int sort = item.sortNo() == null ? 0 : item.sortNo();
|
int updated = serviceMapper.update(null, new UpdateWrapper<MerchantDisplayPolicyMerchant>()
|
.set("sort_no", sort)
|
.eq("id", id)
|
.eq("deleted", 0));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "展示服务状态已变更,请刷新后重试");
|
}
|
shoppingMapper.update(null, new UpdateWrapper<ShoppingTopicMerchant>()
|
.set("sort_no", sort)
|
.eq("merchant_id", id)
|
.eq("deleted", 0));
|
updatedCount++;
|
}
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("updated", updatedCount);
|
return result;
|
}
|
|
private void applyMerchant(MerchantDisplayPolicyMerchant merchant, DisplayServiceSaveRequest request, String merchantType) {
|
merchant.setMerchantName(cleanRequired(request.merchantName(), "展示服务名称不能为空"));
|
merchant.setMerchantType(merchantType);
|
merchant.setContactName(clean(request.contactName()));
|
merchant.setContactMobile(clean(request.contactMobile()));
|
merchant.setLogoUrl(clean(request.logoUrl()));
|
merchant.setCoverUrl(clean(request.coverUrl()));
|
merchant.setAddress(clean(request.address()));
|
merchant.setServiceLabel(clean(request.serviceLabel()));
|
merchant.setRightsText(clean(request.rightsText()));
|
merchant.setUsageInstructions(clean(request.handlingInstructions()));
|
merchant.setSortNo(request.sortNo() == null ? 0 : request.sortNo());
|
int status = request.status() == null ? 1 : request.status();
|
validateStatus(status, "上下架状态只能为1上架或0下架");
|
merchant.setStatus(status);
|
merchant.setRecommend(request.recommend() == null ? 0 : request.recommend());
|
}
|
|
private void upsertShoppingMapping(Long merchantId, String categoryKey, int sortNo) {
|
ShoppingTopicMerchant mapping = shoppingMapper.selectOne(new QueryWrapper<ShoppingTopicMerchant>()
|
.eq("merchant_id", merchantId)
|
.last("limit 1"));
|
if (mapping == null) {
|
ShoppingTopicMerchant entity = new ShoppingTopicMerchant();
|
entity.setMerchantId(merchantId);
|
entity.setCategoryKey(categoryKey);
|
entity.setSortNo(sortNo);
|
entity.setDeleted(0);
|
shoppingMapper.insert(entity);
|
return;
|
}
|
int updated = shoppingMapper.update(null, new UpdateWrapper<ShoppingTopicMerchant>()
|
.set("category_key", categoryKey)
|
.set("sort_no", sortNo)
|
.set("deleted", 0)
|
.eq("id", mapping.getId()));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "服务分类映射已变更,请刷新后重试");
|
}
|
}
|
|
private void removeShoppingMapping(Long merchantId) {
|
shoppingMapper.update(null, new UpdateWrapper<ShoppingTopicMerchant>()
|
.set("deleted", 1)
|
.eq("merchant_id", merchantId)
|
.eq("deleted", 0));
|
}
|
|
private void upsertCardPolicies(Long merchantId, List<DisplayServiceSaveRequest.CardPolicy> policies) {
|
if (policies == null || policies.isEmpty()) {
|
return;
|
}
|
if (policies.stream().anyMatch(Objects::isNull)) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡级别政策不能为空");
|
}
|
Set<String> levels = new LinkedHashSet<>();
|
for (DisplayServiceSaveRequest.CardPolicy policy : policies) {
|
String level = normalizeCardLevel(policy.cardLevel());
|
if (level.isBlank()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡级别不能为空");
|
}
|
levels.add(level);
|
}
|
if (levels.size() != policies.size()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "同一服务不能包含重复的人才卡级别");
|
}
|
for (DisplayServiceSaveRequest.CardPolicy policy : policies) {
|
saveCardPolicy(merchantId, policy);
|
}
|
}
|
|
private void saveCardPolicy(Long merchantId, DisplayServiceSaveRequest.CardPolicy request) {
|
String cardLevel = normalizeCardLevel(request.cardLevel());
|
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)
|
.eq("id", current.getId()));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "政策配置已变更,请刷新后重试");
|
}
|
}
|
|
private MerchantDisplayPolicyMerchant requireMerchant(Long id) {
|
MerchantDisplayPolicyMerchant merchant = serviceMapper.selectOne(new QueryWrapper<MerchantDisplayPolicyMerchant>()
|
.eq("id", id)
|
.eq("deleted", 0)
|
.last("limit 1"));
|
if (merchant == null) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "展示服务不存在");
|
}
|
return merchant;
|
}
|
|
private Map<String, Object> view(Map<String, Object> row) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("serviceId", value(row, "merchantId"));
|
result.put("merchantId", value(row, "merchantId"));
|
result.put("serviceName", text(row, "merchantName"));
|
result.put("merchantName", text(row, "merchantName"));
|
result.put("category", text(row, "category"));
|
result.put("categoryName", categoryName(text(row, "category")));
|
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("serviceLabel", text(row, "serviceLabel"));
|
result.put("rightsText", text(row, "rightsText"));
|
result.put("handlingInstructions", text(row, "handlingInstructions"));
|
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", List.of());
|
result.put("createdAt", value(row, "createdAt"));
|
result.put("updatedAt", value(row, "updatedAt"));
|
return result;
|
}
|
|
private Map<String, Object> policyView(MerchantCardBenefit benefit) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("id", benefit.getId());
|
result.put("cardLevel", benefit.getCardLevel());
|
result.put("cardName", benefit.getCardName());
|
result.put("benefitText", 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 String validateCategory(String category) {
|
String value = clean(category);
|
if (!CATEGORY_KEYS.contains(value)) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "服务分类不正确,仅支持停车、体检、购房、购车、家电、公寓租赁");
|
}
|
return value;
|
}
|
|
private void validateCategoryFilter(String category) {
|
String value = clean(category);
|
if (!value.isBlank() && !CATEGORY_KEYS.contains(value)) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "服务分类不正确,仅支持停车、体检、购房、购车、家电、公寓租赁");
|
}
|
}
|
|
private void validateStatus(Integer status, String message) {
|
if (status != null && status != 0 && status != 1) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, message);
|
}
|
}
|
|
private String categoryName(String key) {
|
return CATEGORIES.stream()
|
.filter(category -> category.key().equals(key))
|
.map(Category::name)
|
.findFirst()
|
.orElse(key);
|
}
|
|
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 Long requireId(Long id, String message) {
|
if (id == null || id <= 0) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, message);
|
}
|
return id;
|
}
|
|
private String cleanRequired(String value, String message) {
|
String text = clean(value);
|
if (text.isBlank()) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, message);
|
}
|
return text;
|
}
|
|
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 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();
|
}
|
|
private record Category(String key, String name) {
|
}
|
}
|