xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsRoleController.java
@@ -1,14 +1,12 @@ package com.xiaopeng.account.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.xiaopeng.account.service.IXpOperationsRoleService; import com.xiaopeng.api.model.account.XpOperationsRole; import com.xiaopeng.common.core.domain.R; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; @@ -53,5 +51,15 @@ .eq("operations_user_id", operationUserId)); return R.ok(list); } /** * 内部调用 * 查询角色关联的运维人员 * @param roleIds * @return */ @PostMapping("/inner/xp-operations-role/getXpOperationsRoleByRoleIds") public R<List<XpOperationsRole>> getXpOperationsRoleByRoleIds(@RequestBody List<Long> roleIds) { List<XpOperationsRole> list = xpOperationsRoleService.list(new LambdaQueryWrapper<XpOperationsRole>().in(XpOperationsRole::getRoleId, roleIds)); return R.ok(list); } } xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpOperationUserClientFallbackFactory.java
@@ -64,6 +64,11 @@ return R.ok(new ArrayList<>()); } @Override public R<List<XpOperationsRole>> getXpOperationsRoleByRoleIds(List<Long> roleIds) { log.error("根据角色ID列表获取运维人员ID列表失败,原因:{}", cause.getMessage()); return R.fail("根据角色ID列表获取运维人员ID列表失败:" + cause.getMessage()); } }; } } xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserClient.java
@@ -78,4 +78,12 @@ */ @GetMapping("/inner/xpOperationsUser/listAllEnabled") R<List<XpOperationsUser>> listAllEnabled(); /** * 查询角色关联的运维人员 * @param roleIds * @return */ @PostMapping("/inner/xp-operations-role/getXpOperationsRoleByRoleIds") R<List<XpOperationsRole>> getXpOperationsRoleByRoleIds(@RequestBody List<Long> roleIds); } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -229,13 +229,14 @@ xpTask.setStatus(0); xpTask.setCreateBy(xpOperationsUser.getNickName()); XpTaskRule taskRule = xpTaskRuleService.lambdaQuery().last("limit 1").one(); XpTaskRuleRole taskRuleRole = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, 9).last("limit 1").one(); if (Objects.isNull(taskRuleRole) || Objects.isNull(taskRule)) { List<XpTaskRuleRole> list = xpTaskRuleRoleService.lambdaQuery().eq(XpTaskRuleRole::getRuleType, 9).list(); if (CollUtil.isEmpty(list) || Objects.isNull(taskRule)) { throw new ServiceException("未配置车辆事故任务分配规则"); } R<List<XpOperationsRole>> xpOperationsRoleResult = xpOperationUserClient.getXpOperationsRoleByRoleId(taskRuleRole.getRoleId()); List<Long> roleIds = list.stream().map(XpTaskRuleRole::getRoleId).collect(Collectors.toList()); R<List<XpOperationsRole>> xpOperationsRoleResult = xpOperationUserClient.getXpOperationsRoleByRoleIds(roleIds); if (CollUtil.isNotEmpty(xpOperationsRoleResult.getData())) { List<Long> userIds = xpOperationsRoleResult.getData().stream().map(XpOperationsRole::getOperationsUserId).collect(Collectors.toList()); List<Long> userIds = xpOperationsRoleResult.getData().stream().map(XpOperationsRole::getOperationsUserId).distinct().collect(Collectors.toList()); xpTask.setHandleUserId(StringUtils.join(userIds, ",")); } // 根据事故处理限时配置设置任务截止时间 xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceManagementController.java
@@ -2,6 +2,7 @@ import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xiaopeng.api.dto.operation.ElectronicFenceAddDto; import com.xiaopeng.api.dto.operation.ElectronicFenceAreaUpdateStatusDto; import com.xiaopeng.api.dto.operation.ElectronicFenceUpdateDto; @@ -26,6 +27,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; import org.apache.shardingsphere.transaction.core.TransactionType; import org.locationtech.jts.geom.Coordinate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -34,6 +37,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import static com.xiaopeng.map.service.serviceimpl.XpElectronicFenceServiceImpl.getPinyinInitials; /** * <p> @@ -58,6 +64,8 @@ @Resource private XpRegionService regionService; @Autowired private XpRegionService xpRegionService; /** * 查询省市管理 */ @@ -166,9 +174,21 @@ electronicFence.setElectronicName("副本_"+electronicFence.getElectronicName()); electronicFence.setId(null); electronicFence.setStatus(0); xpElectronicFenceService.save(electronicFence); List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, id) .list(); //根据经纬度获取当前城市code // 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收 XpRegion xpRegion = xpRegionService.getOne(new LambdaQueryWrapper<XpRegion>() .eq(XpRegion::getCode, electronicFence.getCityCode())); String name = xpRegion.getName(); // 根据城市中文名,截取前两位的首字母并且大写 // 截取前两个字符 String prefix = name.substring(0, 2); // 获取拼音首字母并大写 String result = getPinyinInitials(prefix).toUpperCase(); xpElectronicFenceService.save(electronicFence); electronicFence.setElectronicNumber(result + "-OP-" + String.format("%04d", electronicFence.getId())); xpElectronicFenceService.updateById(electronicFence); for (XpElectronicFenceArea area : electronicFenceAreaList) { area.setId(null); area.setElectronicId(electronicFence.getId()); xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java
@@ -97,4 +97,7 @@ String INVITATION_IMPORT_CODE_CONTAINS_CHINESE = "invitation.import.code.contains.chinese"; String INVITATION_IMPORT_CODE_DUPLICATE = "invitation.import.code.duplicate"; String INVITATION_IMPORT_CODE_EXISTS = "invitation.import.code.exists"; // 邀请码数量上限 String INVITATION_LIMIT_EXCEEDED = "invitation.limit.exceeded"; } xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpInvitationCodeController.java
@@ -6,6 +6,7 @@ import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.TemplateExportParams; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.PhoneUtil; import cn.hutool.core.io.resource.ClassPathResource; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -67,6 +68,7 @@ private MessageSource messageSource; private static final Pattern CHINESE_PATTERN = Pattern.compile("\\p{IsHan}"); private static final long INVITATION_CODE_LIMIT = 500L; @ApiOperation(value = "邀请码管理分页列表",response = XpInvitationCode.class) @PostMapping("/api/v1/invitation/pageList") @@ -82,6 +84,18 @@ @ApiOperation(value = "生成邀请码") @Transactional public R add(@RequestBody InvitationAddDto dto){ long existing = xpInvitationCodeService.count(); long incoming = Arrays.stream(Optional.ofNullable(dto.getPhones()).orElse("").split(",")) .map(String::trim) .filter(StringUtils::hasLength) .count(); if (existing >= INVITATION_CODE_LIMIT || existing + incoming > INVITATION_CODE_LIMIT) { String msg = messageSource.getMessage(I18nKeyConstant.INVITATION_LIMIT_EXCEEDED, new Object[]{INVITATION_CODE_LIMIT, existing, incoming}, I18nKeyConstant.INVITATION_LIMIT_EXCEEDED, LocaleContextHolder.getLocale()); return R.fail(msg); } return xpInvitationCodeService.add(dto.getPhones()); } /** @@ -158,6 +172,16 @@ } if (CollectionUtils.isEmpty(invitationCodeImportExcels)) { return R.fail(messageSource.getMessage(I18nKeyConstant.INVITATION_IMPORT_DATA_EMPTY, null, I18nKeyConstant.INVITATION_IMPORT_DATA_EMPTY, LocaleContextHolder.getLocale())); } long existing = xpInvitationCodeService.count(); long incoming = invitationCodeImportExcels.size(); if (existing >= INVITATION_CODE_LIMIT || existing + incoming > INVITATION_CODE_LIMIT) { String msg = messageSource.getMessage(I18nKeyConstant.INVITATION_LIMIT_EXCEEDED, new Object[]{INVITATION_CODE_LIMIT, existing, incoming}, I18nKeyConstant.INVITATION_LIMIT_EXCEEDED, LocaleContextHolder.getLocale()); return R.fail(msg); } // 批量校验:账号必须为手机号;邀请码不能为空且不能包含中文;同时校验文件内重复/数据库已存在 @@ -320,5 +344,16 @@ xpInvitationCodeService.removeBatchByIds(longs); return R.ok(); } @ApiOperation(value = "校验生成邀请码的手机号是否合法") @PostMapping("/api/v1/invitation/validateCode") public R<Boolean> validateCode(@RequestParam String phone){ if (!PhoneUtil.isMobile(phone)){ return R.ok(false); } List<XpInvitationCode> existsList = xpInvitationCodeService.lambdaQuery() .in(XpInvitationCode::getBindAccount, phone) .list(); return R.ok(CollUtil.isEmpty(existsList)); } } xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpWhitelistController.java
@@ -242,7 +242,7 @@ recordFail(reasonCountMap, reasonSampleMap, "账号为空", "第" + excelRowNumber + "行", sampleLimitPerReason); continue; } if (!PhoneUtil.isPhone(account)) { if (!PhoneUtil.isMobile(account)) { recordFail(reasonCountMap, reasonSampleMap, "账号格式不正确", account, sampleLimitPerReason); continue; } @@ -406,9 +406,10 @@ public void exportExcel(@RequestBody WhitelistPageListQuery query) { List<XpWhitelist> exportList = xpWhitelistService.exportList(query); List<XpWhitelistExportExcel> xpWhitelistExportExcels = new ArrayList<>(); long sequence = 1L; for (XpWhitelist xpWhitelist : exportList) { XpWhitelistExportExcel xpWhitelistExportExcel = new XpWhitelistExportExcel(); xpWhitelistExportExcel.setId(xpWhitelist.getId()); xpWhitelistExportExcel.setId(sequence++); xpWhitelistExportExcel.setAccount(xpWhitelist.getAccount()); xpWhitelistExportExcel.setNickName(xpWhitelist.getNickName()); xpWhitelistExportExcel.setStatus(xpWhitelist.getStatus()); xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpInvitationCodeServiceImpl.java
@@ -5,6 +5,7 @@ import com.xiaopeng.api.model.operation.XpWhitelist; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.operation.constant.I18nKeyConstant; import com.xiaopeng.operation.mapper.XpInvitationCodeMapper; import com.xiaopeng.operation.mapper.XpWhitelistMapper; import com.xiaopeng.operation.query.InvitationPageListQuery; @@ -31,6 +32,7 @@ @Autowired private XpWhitelistMapper xpWhitelistMapper; private static final long INVITATION_CODE_LIMIT = 500L; @Override public PageInfo<XpInvitationCode> pageList(InvitationPageListQuery query) { PageInfo<XpInvitationCode> pageInfo = new PageInfo<>(query.getPageNum(),query.getPageSize()); @@ -48,6 +50,16 @@ @Override public R add(String phones) { long existing = this.count(); long incoming = Arrays.stream(Optional.ofNullable(phones).orElse("").split(",")) .map(String::trim) .filter(StringUtils::hasLength) .count(); if (existing >= INVITATION_CODE_LIMIT || existing + incoming > INVITATION_CODE_LIMIT) { return R.fail(String.format("系统最多只能有%d个邀请码,当前已有%d个,本次新增%d个,无法生成", INVITATION_CODE_LIMIT, existing, incoming), I18nKeyConstant.INVITATION_LIMIT_EXCEEDED); } Map<String, Long> codeMap = this.list().stream() .collect(Collectors.toMap(XpInvitationCode::getInvitationCode, XpInvitationCode::getId)); if (StringUtils.hasLength(phones)){ xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpSystemSettingServiceImpl.java
@@ -66,6 +66,7 @@ HotFixInfoVo hotFixInfoVo = new HotFixInfoVo(); hotFixInfoVo.setHotfixStatus(xpSystemSetting.getHotfixStatus() == null ? 0 : xpSystemSetting.getHotfixStatus()); hotFixInfoVo.setHotfixStartTime(xpSystemSetting.getHotfixStartTime()); hotFixInfoVo.setHotfixEndTime(xpSystemSetting.getHotfixEndTime()); redisService.setCacheObject("HOT_FIX_INFO", hotFixInfoVo); } } xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpWhitelistServiceImpl.java
@@ -8,7 +8,6 @@ import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.ip.IpUtils; import com.xiaopeng.common.core.web.domain.AjaxResult; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.operation.constant.I18nKeyConstant; import com.xiaopeng.operation.dto.WhitelistAddDto; @@ -23,8 +22,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** @@ -56,41 +60,164 @@ @Override public R addWhitelist(WhitelistAddDto dto, SysUser sysUser) { XpWhitelist old = this.lambdaQuery().eq(XpWhitelist::getAccount, dto.getAccount()).last("limit 1").one(); if (old != null) { return R.fail("该账号已存在"); List<String> accounts = Arrays.stream(dto.getAccount().split(",")) .map(String::trim) .filter(StringUtils::hasLength) .collect(Collectors.toList()); if (CollUtil.isEmpty(accounts)) { return R.fail("所有帐号添加失败,请检查帐号格式或唯一性"); } int sampleLimitPerReason = 20; int totalCount = accounts.size(); Map<String, Long> reasonCountMap = new HashMap<>(); Map<String, LinkedHashSet<String>> reasonSampleMap = new HashMap<>(); Set<String> seenAccounts = new HashSet<>(); List<String> candidates = new ArrayList<>(); for (String account : accounts) { if (!PhoneUtil.isMobile(account)) { recordFail(reasonCountMap, reasonSampleMap, "帐号格式不正确", account, sampleLimitPerReason); continue; } if (!seenAccounts.add(account)) { recordFail(reasonCountMap, reasonSampleMap, "文件内重复", account, sampleLimitPerReason); continue; } candidates.add(account); } Set<String> existsAccounts = new HashSet<>(); if (CollUtil.isNotEmpty(candidates)) { existsAccounts = this.lambdaQuery() .select(XpWhitelist::getAccount) .in(XpWhitelist::getAccount, candidates) .list() .stream() .map(XpWhitelist::getAccount) .filter(StringUtils::hasLength) .collect(Collectors.toSet()); } List<XpWhitelist> addList = new ArrayList<>(); List<String> accounts = Arrays.asList(dto.getAccount().split(",")); if (CollUtil.isNotEmpty(accounts)) { String notValidPhoneNumber = accounts.stream().filter(item -> !PhoneUtil.isPhone(item)).collect(Collectors.joining(",")); if (org.apache.commons.lang3.StringUtils.isNotBlank(notValidPhoneNumber)) { throw new ServiceException("以下帐号格式不正确:" + notValidPhoneNumber, I18nKeyConstant.ACCOUNT_FORMAT_ERROR, new Object[]{notValidPhoneNumber}); for (String account : candidates) { if (existsAccounts.contains(account)) { recordFail(reasonCountMap, reasonSampleMap, "帐号已存在", account, sampleLimitPerReason); continue; } StringBuilder operationContent = new StringBuilder("新增账号:"); StringBuilder accountBuilder = new StringBuilder(); for (String account : accounts) { XpWhitelist xpWhitelist = new XpWhitelist(); xpWhitelist.setAccount(account); xpWhitelist.setStatus(true); xpWhitelist.setIsPriorityDispatch(dto.getIsPriorityDispatch()); xpWhitelist.setWhitelistSource(1); xpWhitelist.setCreateBy(sysUser.getCreateBy()); addList.add(xpWhitelist); accountBuilder.append("\"").append(account).append("\"").append(","); } XpWhitelistOperlog xpWhitelistOperlog = new XpWhitelistOperlog(); xpWhitelistOperlog.setOperationName(sysUser.getNickName()); xpWhitelistOperlog.setOperationAccount(sysUser.getPhoneNumber()); xpWhitelistOperlog.setOperationType(1); xpWhitelistOperlog.setOperationIp(IpUtils.getIpAddr()); xpWhitelistOperlog.setOperationAccountId(sysUser.getId()); StringBuilder content = operationContent.append(accountBuilder.substring(0, accountBuilder.length() - 1)); xpWhitelistOperlog.setOperationContent(content.toString()); xpWhitelistOperlogMapper.insert(xpWhitelistOperlog); this.saveBatch(addList); XpWhitelist xpWhitelist = new XpWhitelist(); xpWhitelist.setAccount(account); xpWhitelist.setStatus(true); xpWhitelist.setIsPriorityDispatch(dto.getIsPriorityDispatch()); xpWhitelist.setWhitelistSource(1); xpWhitelist.setCreateBy(sysUser.getNickName()); addList.add(xpWhitelist); } return R.ok(); int successCount = addList.size(); int failCount = totalCount - successCount; String message; if (failCount == 0) { message = "成功添加" + successCount + "条白名单记录"; } else if (successCount == 0) { message = "所有帐号添加失败,请检查帐号格式或唯一性"; } else { message = "成功加" + successCount + "条," + failCount + "条添加失败,失败原因:" + buildReasonDetail(reasonCountMap, reasonSampleMap); } if (successCount == 0) { return R.fail(message); } this.saveBatch(addList); StringBuilder operationContent = new StringBuilder("新增账号:"); StringBuilder accountBuilder = new StringBuilder(); for (XpWhitelist item : addList) { accountBuilder.append("\"").append(item.getAccount()).append("\"").append(","); } if (accountBuilder.length() > 0) { operationContent.append(accountBuilder.substring(0, accountBuilder.length() - 1)); } if (failCount > 0) { operationContent.append(";失败").append(failCount).append("条"); operationContent.append(";原因").append(buildReasonSummary(reasonCountMap)); } XpWhitelistOperlog xpWhitelistOperlog = new XpWhitelistOperlog(); xpWhitelistOperlog.setOperationName(sysUser.getNickName()); xpWhitelistOperlog.setOperationAccount(sysUser.getPhoneNumber()); xpWhitelistOperlog.setOperationType(1); xpWhitelistOperlog.setOperationIp(IpUtils.getIpAddr()); xpWhitelistOperlog.setOperationAccountId(sysUser.getId()); xpWhitelistOperlog.setOperationContent(truncate(operationContent.toString(), 200)); xpWhitelistOperlogMapper.insert(xpWhitelistOperlog); return R.ok(null, message); } private static void recordFail(Map<String, Long> reasonCountMap, Map<String, LinkedHashSet<String>> reasonSampleMap, String reason, String sample, int sampleLimitPerReason) { reasonCountMap.merge(reason, 1L, Long::sum); if (!StringUtils.hasLength(sample) || sampleLimitPerReason <= 0) { return; } LinkedHashSet<String> samples = reasonSampleMap.computeIfAbsent(reason, k -> new LinkedHashSet<>()); if (samples.size() >= sampleLimitPerReason) { return; } samples.add(sample); } private static String buildReasonSummary(Map<String, Long> reasonCountMap) { if (reasonCountMap == null || reasonCountMap.isEmpty()) { return "请检查帐号格式或唯一性"; } return reasonCountMap.entrySet().stream() .sorted(Comparator.<Map.Entry<String, Long>>comparingLong(e -> e.getValue() == null ? 0L : e.getValue()) .reversed() .thenComparing(Map.Entry::getKey)) .map(e -> e.getKey() + "(" + (e.getValue() == null ? 0L : e.getValue()) + ")") .collect(Collectors.joining(",")); } private static String buildReasonDetail(Map<String, Long> reasonCountMap, Map<String, LinkedHashSet<String>> reasonSampleMap) { if (reasonCountMap == null || reasonCountMap.isEmpty()) { return "请检查帐号格式或唯一性"; } Map<String, LinkedHashSet<String>> safeSamples = reasonSampleMap == null ? new HashMap<>() : reasonSampleMap; return reasonCountMap.entrySet().stream() .sorted(Comparator.<Map.Entry<String, Long>>comparingLong(e -> e.getValue() == null ? 0L : e.getValue()) .reversed() .thenComparing(Map.Entry::getKey)) .map(entry -> { String reason = entry.getKey(); long count = entry.getValue() == null ? 0L : entry.getValue(); LinkedHashSet<String> samples = safeSamples.getOrDefault(reason, new LinkedHashSet<>()); if (samples.isEmpty()) { return reason + "(" + count + ")"; } long remaining = count - samples.size(); String suffix = remaining > 0 ? "等" + remaining + "个" : ""; String suffixPart = StringUtils.hasLength(suffix) ? ("," + suffix) : ""; return reason + "(" + count + ")[" + String.join(",", samples) + suffixPart + "]"; }) .collect(Collectors.joining(";")); } private static String truncate(String value, int maxLength) { if (value == null || maxLength <= 0) { return value; } if (value.length() <= maxLength) { return value; } return value.substring(0, maxLength); } @Override xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -36,3 +36,4 @@ invitation.import.code.contains.chinese=Row {0}: invitation code contains Chinese characters: {1} invitation.import.code.duplicate=Duplicate invitation codes in file: {0} invitation.import.code.exists=The following invitation codes already exist: {0} invitation.limit.exceeded=The system can hold up to {0} invitation codes. Current: {1}, incoming: {2}. Generation is not allowed. xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -36,3 +36,4 @@ invitation.import.code.contains.chinese=第{0}行邀请码包含中文:{1} invitation.import.code.duplicate=导入文件中邀请码重复:{0} invitation.import.code.exists=以下邀请码已存在:{0} invitation.limit.exceeded=系统最多只能有{0}个邀请码,当前已有{1}个,本次新增{2}个,无法生成 xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -630,6 +630,21 @@ xpVehicleClient.updateVehicleStatus(new UpdateVehicleStateDto(xpVehicle.getVehicleVin(), 0)); } // 消息推送 Map<String, Object> params = new HashMap<>(); Map<String, Object> content = new HashMap<>(); content.put("orderId", order.getId().toString()); content.put("status", OrderStatusEnum.CANCELED.getCode()); params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc()); params.put("content", com.alibaba.fastjson2.JSONObject.toJSONString(content)); params.put("timestamp", new Date().getTime() + ""); String message = JSONObject.toJSONString(params); SendMessageDTO sendMessageDTO = new SendMessageDTO(); sendMessageDTO.setMessage(message); sendMessageDTO.setAppUserId(String.valueOf(order.getAppUserId())); xpPushMessageClient.sendStatus(sendMessageDTO); // 下发命令 if (order.getStatus() > 1) { PushOrderDto pushOrderDto = new PushOrderDto(); xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/constant/I18nKeyConstant.java
@@ -40,4 +40,14 @@ String VEHICLE_TEMPERATURE_DOWN = "vehicle.temperature.down"; // ========== 车辆新增/编辑校验 ========== String VEHICLE_CODE_INVALID = "vehicle.code.invalid"; String VEHICLE_CODE_EXISTS = "vehicle.code.exists"; String VEHICLE_VIN_INVALID = "vehicle.vin.invalid"; String VEHICLE_VIN_EXISTS = "vehicle.vin.exists"; String VEHICLE_PLATE_INVALID = "vehicle.plate.invalid"; String VEHICLE_PLATE_EXISTS = "vehicle.plate.exists"; String VEHICLE_COLOR_INVALID = "vehicle.color.invalid"; String VEHICLE_ELECTRONIC_NOT_EXISTS = "vehicle.electronic.not.exists"; } xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -2,6 +2,7 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Validator; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -58,6 +59,7 @@ import java.time.LocalTime; import java.util.*; import java.util.stream.Collectors; import java.util.regex.Pattern; /** * <p> @@ -70,6 +72,9 @@ @Slf4j @Service public class XpVehicleServiceImpl extends ServiceImpl<XpVehicleMapper, XpVehicle> implements IXpVehicleService { private static final Pattern VEHICLE_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_]{5}$"); private static final Pattern VEHICLE_VIN_PATTERN = Pattern.compile("^[A-Z0-9]{17}$"); @Autowired private VehicleControlUtil vehicleControlUtil; @@ -309,6 +314,18 @@ @Override @Transactional(rollbackFor = Exception.class) public void addVehicle(VehicleAddDTO dto) { String normalizedVehicleCode = trimToNull(dto.getVehicleCode()); String normalizedVin = normalizeVin(dto.getVehicleVin()); String normalizedPlateNumber = trimToNull(dto.getPlateNumber()); String normalizedVehicleColor = trimToNull(dto.getVehicleColor()); validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, null); validateElectronicFenceExists(dto.getElectronicId()); dto.setVehicleCode(normalizedVehicleCode); dto.setVehicleVin(normalizedVin); dto.setPlateNumber(normalizedPlateNumber); dto.setVehicleColor(normalizedVehicleColor); // DTO转实体 XpVehicle xpVehicle = new XpVehicle(); BeanUtil.copyProperties(dto, xpVehicle); @@ -329,6 +346,18 @@ @Override @Transactional(rollbackFor = Exception.class) public void editVehicle(VehicleEditDTO dto) { String normalizedVehicleCode = trimToNull(dto.getVehicleCode()); String normalizedVin = normalizeVin(dto.getVehicleVin()); String normalizedPlateNumber = trimToNull(dto.getPlateNumber()); String normalizedVehicleColor = trimToNull(dto.getVehicleColor()); validateAndNormalizeIdentityFields(normalizedVehicleCode, normalizedVin, normalizedPlateNumber, normalizedVehicleColor, dto.getId()); validateElectronicFenceExists(dto.getElectronicId()); dto.setVehicleCode(normalizedVehicleCode); dto.setVehicleVin(normalizedVin); dto.setPlateNumber(normalizedPlateNumber); dto.setVehicleColor(normalizedVehicleColor); // DTO转实体 XpVehicle xpVehicle = new XpVehicle(); BeanUtil.copyProperties(dto, xpVehicle); @@ -339,6 +368,74 @@ this.updateById(xpVehicle); } private void validateAndNormalizeIdentityFields(String vehicleCode, String vehicleVin, String plateNumber, String vehicleColor, Long excludeId) { if (StringUtils.isBlank(vehicleCode) || !VEHICLE_CODE_PATTERN.matcher(vehicleCode).matches()) { throw new ServiceException("车辆编码格式不正确", I18nKeyConstant.VEHICLE_CODE_INVALID, new Object[]{vehicleCode}); } if (StringUtils.isBlank(vehicleVin) || !VEHICLE_VIN_PATTERN.matcher(vehicleVin).matches()) { throw new ServiceException("车辆VIN码格式不正确", I18nKeyConstant.VEHICLE_VIN_INVALID, new Object[]{vehicleVin}); } if (StringUtils.isBlank(plateNumber) || !Validator.isPlateNumber(plateNumber)) { throw new ServiceException("车牌号格式不正确", I18nKeyConstant.VEHICLE_PLATE_INVALID, new Object[]{plateNumber}); } if (StringUtils.isNotBlank(vehicleColor) && vehicleColor.length() > 2) { throw new ServiceException("车辆颜色长度不能超过2", I18nKeyConstant.VEHICLE_COLOR_INVALID, new Object[]{vehicleColor}); } boolean vehicleCodeExists = lambdaQuery() .eq(XpVehicle::getVehicleCode, vehicleCode) .ne(excludeId != null, XpVehicle::getId, excludeId) .last("limit 1") .one() != null; if (vehicleCodeExists) { throw new ServiceException("车辆编码已存在", I18nKeyConstant.VEHICLE_CODE_EXISTS, new Object[]{vehicleCode}); } boolean vehicleVinExists = lambdaQuery() .eq(XpVehicle::getVehicleVin, vehicleVin) .ne(excludeId != null, XpVehicle::getId, excludeId) .last("limit 1") .one() != null; if (vehicleVinExists) { throw new ServiceException("车辆VIN码已存在", I18nKeyConstant.VEHICLE_VIN_EXISTS, new Object[]{vehicleVin}); } boolean plateNumberExists = lambdaQuery() .eq(XpVehicle::getPlateNumber, plateNumber) .ne(excludeId != null, XpVehicle::getId, excludeId) .last("limit 1") .one() != null; if (plateNumberExists) { throw new ServiceException("车牌号已存在", I18nKeyConstant.VEHICLE_PLATE_EXISTS, new Object[]{plateNumber}); } } private void validateElectronicFenceExists(Long electronicId) { if (electronicId == null) { return; } R<List<XpElectronicFence>> result = xpElectronicClient.listByIds(Collections.singleton(electronicId)); boolean exists = result != null && result.getData() != null && result.getData().stream().anyMatch(fence -> fence != null && electronicId.equals(fence.getId())); if (!exists) { throw new ServiceException("运营区域不存在", I18nKeyConstant.VEHICLE_ELECTRONIC_NOT_EXISTS, new Object[]{electronicId}); } } private static String normalizeVin(String vehicleVin) { String trimmed = trimToNull(vehicleVin); return trimmed == null ? null : trimmed.toUpperCase(Locale.ROOT); } private static String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } @Override @Transactional(rollbackFor = Exception.class) public void improveInfo(VehicleImproveInfoDTO dto) { xp-robotaxi-vehicle-convoy-boot/src/main/resources/i18n/messages_en_US.properties
@@ -25,4 +25,12 @@ vehicle.not.p.gear=The trunk cannot be opened when the vehicle is not in P gear vehicle.temperature.up=The highest temperature is 28°C vehicle.temperature.down=The lowest temperature is 22°C vehicle.code.invalid=Invalid vehicle code (5 chars, letters/numbers/underscore only): {0} vehicle.code.exists=Vehicle code already exists: {0} vehicle.vin.invalid=Invalid VIN (17 chars, uppercase letters and digits only): {0} vehicle.vin.exists=VIN already exists: {0} vehicle.plate.invalid=Invalid plate number: {0} vehicle.plate.exists=Plate number already exists: {0} vehicle.color.invalid=Vehicle color length must be <= 2: {0} vehicle.electronic.not.exists=Selected operating area does not exist: {0} vehicle.distance.too.long=Too far away, unable to open xp-robotaxi-vehicle-convoy-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -26,4 +26,12 @@ vehicle.not.p.gear=车辆非P档,无法开启后备箱 vehicle.temperature.up=温度最高28℃ vehicle.temperature.down=温度最低22℃ vehicle.code.invalid=车辆编码格式不正确,仅支持5位字母、数字、下划线:{0} vehicle.code.exists=车辆编码已存在:{0} vehicle.vin.invalid=车辆VIN码格式不正确,仅支持17位大写字母与数字:{0} vehicle.vin.exists=车辆VIN码已存在:{0} vehicle.plate.invalid=车牌号格式不正确:{0} vehicle.plate.exists=车牌号已存在:{0} vehicle.color.invalid=车辆颜色长度不能超过2:{0} vehicle.electronic.not.exists=选择的运营区域不存在:{0} vehicle.distance.too.long=距离过远,无法打开