package com.talentcard.server.service;
|
|
import java.net.URI;
|
import java.util.LinkedHashMap;
|
import java.util.Locale;
|
import java.util.Map;
|
|
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.TransportCarWashService;
|
import com.talentcard.server.mapper.TransportCarWashServiceMapper;
|
import com.talentcard.server.request.CarWashJumpConfigRequest;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class CarWashJumpConfigAdminService {
|
private final TransportCarWashServiceMapper serviceMapper;
|
|
public CarWashJumpConfigAdminService(TransportCarWashServiceMapper serviceMapper) {
|
this.serviceMapper = serviceMapper;
|
}
|
|
@Transactional(readOnly = true)
|
public PageResult<Map<String, Object>> page(Long merchantId, String keyword, int page, int size) {
|
int safePage = Math.max(page, 1);
|
int safeSize = Math.min(Math.max(size, 1), 100);
|
String cleanedKeyword = clean(keyword);
|
var records = serviceMapper.selectConfigPage(merchantId, cleanedKeyword, safeSize, (safePage - 1) * safeSize)
|
.stream()
|
.map(this::view)
|
.toList();
|
Long total = serviceMapper.countConfigPage(merchantId, cleanedKeyword);
|
return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total);
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long merchantId) {
|
Map<String, Object> row = serviceMapper.selectConfigDetail(requireMerchantId(merchantId));
|
if (row == null || row.isEmpty()) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "洗车商家不存在");
|
}
|
return view(row);
|
}
|
|
@Transactional
|
public Map<String, Object> save(Long merchantId, CarWashJumpConfigRequest request) {
|
Long id = requireMerchantId(merchantId);
|
Long merchantCount = serviceMapper.countCarWashMerchant(id);
|
if (merchantCount == null || merchantCount == 0) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "洗车商家不存在");
|
}
|
JumpConfig config = validate(request);
|
TransportCarWashService current = serviceMapper.selectOne(new QueryWrapper<TransportCarWashService>()
|
.eq("merchant_id", id)
|
.eq("deleted", 0)
|
.last("limit 1 for update"));
|
if (current == null) {
|
current = new TransportCarWashService();
|
current.setMerchantId(id);
|
current.setTalentLevels("卓越人才卡、杰出人才卡、菁英人才卡");
|
current.setServicePointText("请咨询商户");
|
current.setTipText("到店后请按现场指引操作");
|
current.setDeleted(0);
|
apply(current, config, request.status());
|
serviceMapper.insert(current);
|
} else {
|
int updated = serviceMapper.update(null, new UpdateWrapper<TransportCarWashService>()
|
.set("external_link_type", config.type())
|
.set("external_mini_program_app_id", config.appId())
|
.set("external_mini_program_path", config.path())
|
.set("external_web_url", config.webUrl())
|
.set("status", normalizeStatus(request.status()))
|
.eq("id", current.getId())
|
.eq("deleted", 0));
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "洗车跳转配置已变化,请刷新后重试");
|
}
|
}
|
return detail(id);
|
}
|
|
private void apply(TransportCarWashService service, JumpConfig config, Integer status) {
|
service.setExternalLinkType(config.type());
|
service.setExternalMiniProgramAppId(config.appId());
|
service.setExternalMiniProgramPath(config.path());
|
service.setExternalWebUrl(config.webUrl());
|
service.setStatus(normalizeStatus(status));
|
}
|
|
private JumpConfig validate(CarWashJumpConfigRequest request) {
|
String type = normalizeType(request.externalLinkType());
|
if ("miniProgram".equals(type)) {
|
String appId = clean(request.externalMiniProgramAppId());
|
if (!appId.matches("^wx[a-zA-Z0-9]{16}$")) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "第三方小程序AppId格式不正确");
|
}
|
return new JumpConfig(type, appId, clean(request.externalMiniProgramPath()), "");
|
}
|
if ("web".equals(type)) {
|
String webUrl = clean(request.externalWebUrl());
|
if (!validHttpsUrl(webUrl)) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "第三方网页地址必须是有效的HTTPS地址");
|
}
|
return new JumpConfig(type, "", "", webUrl);
|
}
|
return new JumpConfig("", "", "", "");
|
}
|
|
private String normalizeType(String raw) {
|
String value = clean(raw).toLowerCase(Locale.ROOT).replace("_", "").replace("-", "");
|
return switch (value) {
|
case "miniprogram", "mini", "wechatminiprogram", "小程序" -> "miniProgram";
|
case "web", "h5", "网页" -> "web";
|
case "none", "no", "off", "关闭", "不跳转" -> "";
|
default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "跳转类型仅支持第三方小程序、网页或不跳转");
|
};
|
}
|
|
private boolean validHttpsUrl(String value) {
|
if (value.isBlank()) {
|
return false;
|
}
|
try {
|
URI uri = URI.create(value);
|
return "https".equalsIgnoreCase(uri.getScheme()) && uri.getHost() != null && uri.getUserInfo() == null;
|
} catch (IllegalArgumentException exception) {
|
return false;
|
}
|
}
|
|
private int normalizeStatus(Integer status) {
|
if (status == null) {
|
return 1;
|
}
|
if (status != 0 && status != 1) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "服务状态只能为0停用或1启用");
|
}
|
return status;
|
}
|
|
private Long requireMerchantId(Long merchantId) {
|
if (merchantId == null || merchantId <= 0) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "洗车商家ID不能为空");
|
}
|
return merchantId;
|
}
|
|
private Map<String, Object> view(Map<String, Object> row) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("merchantId", value(row, "merchantId", "merchant_id"));
|
result.put("merchantName", text(row, "merchantName", "merchant_name"));
|
result.put("contactMobile", text(row, "contactMobile", "contact_mobile"));
|
result.put("address", text(row, "address"));
|
result.put("coverUrl", text(row, "coverUrl", "cover_url"));
|
result.put("merchantStatus", intValue(row, "merchantStatus", "merchant_status"));
|
result.put("configId", value(row, "configId", "config_id"));
|
result.put("externalLinkType", text(row, "externalLinkType", "external_link_type"));
|
result.put("externalMiniProgramAppId", text(row, "externalMiniProgramAppId", "external_mini_program_app_id"));
|
result.put("externalMiniProgramPath", text(row, "externalMiniProgramPath", "external_mini_program_path"));
|
result.put("externalWebUrl", text(row, "externalWebUrl", "external_web_url"));
|
result.put("serviceStatus", intValue(row, "serviceStatus", "service_status"));
|
result.put("updatedAt", value(row, "updatedAt", "updated_at"));
|
return result;
|
}
|
|
private Object value(Map<String, Object> row, String... keys) {
|
for (String key : keys) {
|
if (row.containsKey(key)) {
|
return row.get(key);
|
}
|
}
|
return null;
|
}
|
|
private String text(Map<String, Object> row, String... keys) {
|
Object value = value(row, keys);
|
return value == null ? "" : String.valueOf(value).trim();
|
}
|
|
private int intValue(Map<String, Object> row, String... keys) {
|
Object value = value(row, keys);
|
return value instanceof Number number ? number.intValue() : 0;
|
}
|
|
private String clean(String value) {
|
return value == null ? "" : value.trim();
|
}
|
|
private record JumpConfig(String type, String appId, String path, String webUrl) {
|
}
|
}
|