package com.xiaopeng.map.controller; import com.xiaopeng.api.dto.operation.PointAddDto; import com.xiaopeng.api.dto.operation.PointUpdateDto; import com.xiaopeng.api.dto.operation.XpPointEnableDisableDto; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.operation.XpPoint; import com.xiaopeng.api.feignClient.operation.XpCruisePlanClient; import com.xiaopeng.api.query.operation.PointListQuery; import com.xiaopeng.api.query.operation.PointPageListListQuery; import com.xiaopeng.map.constant.I18nKeyConstant; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.WebUtils; import com.xiaopeng.common.core.web.controller.BaseController; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.api.dto.map.XpPointExportExcelQuery; import com.xiaopeng.map.exportExcel.XpPointExportExcel; import com.xiaopeng.map.mangodb.service.XpGeoFencingPolygonService; import com.xiaopeng.map.service.XpElectronicFenceAreaService; import com.xiaopeng.map.service.XpElectronicFenceService; import com.xiaopeng.map.service.XpPointService; import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Workbook; import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; import org.apache.shardingsphere.transaction.core.TransactionType; import org.springframework.beans.BeanUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/api/v1/point") @Api(tags = "点位管理") public class XpPointManagementController extends BaseController { private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Resource private XpPointService xpPointService; @Resource private XpElectronicFenceAreaService xpElectronicFenceAreaService; @Resource private XpElectronicFenceService xpElectronicFenceService; @Resource private XpGeoFencingPolygonService xpGeoFencingPolygonService; @Resource private XpCruisePlanClient xpCruisePlanClient; @ApiOperation(value = "获取所有运营中的区域", response = XpElectronicFenceArea.class) @PostMapping("/listElectronicFenceArea") public R> listElectronicFenceArea() { // 获取所有启用的电子围栏 List electronicFences = xpElectronicFenceService.lambdaQuery() .eq(XpElectronicFence::getStatus, 2).list(); List electronicFenceIds = electronicFences.stream().map(XpElectronicFence::getId).collect(Collectors.toList()); if (electronicFenceIds.isEmpty()) { return R.ok(new ArrayList()); } // 获取电子围栏下启用的运营区域 List electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().in(XpElectronicFenceArea::getElectronicId, electronicFenceIds) .eq(XpElectronicFenceArea::getAreaStatus, 1).list(); return R.ok(electronicFenceAreaList); } @ApiOperation(value = "点位管理列表", response = XpPoint.class) @PostMapping("/list") public R> list(@RequestBody PointListQuery query) { List pageList = xpPointService.listAll(query); return R.ok(pageList); } @ApiOperation(value = "点位管理分页列表", response = XpPoint.class) @PostMapping("/pageList") public R> pageList(@RequestBody PointPageListListQuery query) { PageInfo pageList = xpPointService.pageList(query); return R.ok(pageList); } @PostMapping("/getByIds") public R> getByIds(@RequestBody XpPointExportExcelQuery query) { List list = xpPointService.lambdaQuery().in(XpPoint::getId, query.getPointIds()).list(); return R.ok(list); } @ApiOperation(value = "点位导出") @PostMapping("/exportExcel") public void exportExcel(@RequestBody XpPointExportExcelQuery query) { List pointIds = query == null ? Collections.emptyList() : query.getPointIds(); List distinctIds = pointIds == null ? Collections.emptyList() : pointIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList()); List points = distinctIds.isEmpty() ? Collections.emptyList() : xpPointService.lambdaQuery().in(XpPoint::getId, distinctIds).list(); Map pointById = points.stream() .filter(p -> p.getId() != null) .collect(Collectors.toMap(XpPoint::getId, Function.identity(), (a, b) -> a)); List exportList = new ArrayList<>(points.size()); for (Long id : distinctIds) { XpPoint xpPoint = pointById.get(id); if (xpPoint == null) { continue; } exportList.add(toExportRow(xpPoint)); } HttpServletResponse response = WebUtils.response(); if (response == null) { throw new ServiceException("导出失败:未获取到响应对象"); } response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); try { String fileName = URLEncoder.encode("点位列表.xls", StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName); try (Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), XpPointExportExcel.class, exportList); ServletOutputStream outputStream = response.getOutputStream()) { workbook.write(outputStream); } } catch (IOException e) { log.error("点位导出失败 pointIds={}", distinctIds, e); throw new ServiceException("点位导出失败"); } } /** * 新增点位 */ @PostMapping("/add") @ResponseBody @ApiOperation(value = "新增点位") public R add(@Validated @RequestBody PointAddDto pointAddDto) { if (xpGeoFencingPolygonService.isInFence(pointAddDto.getPointLon(), pointAddDto.getPointLat(), pointAddDto.getFenceId(), "prohibited_area")) { return R.ok("in_prohibited_area"); } return R.ok(xpPointService.addPointWithGeneratedNumber(pointAddDto)); } /** * 批量删除点位 */ @GetMapping("/deletePoint") @ResponseBody @ApiOperation(value = "删除点位") public R deletePoint(@RequestParam("ids") String ids) { List pointIds = Arrays.stream(ids.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .distinct() .collect(Collectors.toList()); if (pointIds.isEmpty()) { return R.ok(); } R existsResult = xpCruisePlanClient.existsCruisePlanPointByPointIds(pointIds); if (existsResult == null || existsResult.getData() == null) { throw new ServiceException("系统异常", I18nKeyConstant.SYSTEM_EXCEPTION); } if (Boolean.TRUE.equals(existsResult.getData())) { throw new ServiceException("操作失败,该点位有关联的巡游计划点位", I18nKeyConstant.POINT_DELETE_HAS_CRUISE_PLAN_POINT); } xpPointService.removeBatchByIds(pointIds); return R.ok(); } /** * 编辑点位 */ @PostMapping("/edit") @ApiOperation(value = "编辑点位") @Transactional @ShardingSphereTransactionType(TransactionType.BASE) public R edit(@Validated @RequestBody PointUpdateDto pointAddDto) { XpPoint xpPoint = xpPointService.getById(pointAddDto.getId()); if (xpPoint == null) { throw new ServiceException("系统异常", I18nKeyConstant.SYSTEM_EXCEPTION); } BeanUtils.copyProperties(pointAddDto, xpPoint); if (Integer.valueOf(2).equals(pointAddDto.getOperationStatus()) && Integer.valueOf(5).equals(pointAddDto.getVerificationStatus())) { Long electronicAreaId = xpPoint.getElectronicAreaId(); if (electronicAreaId == null) { throw new ServiceException("当前点位不在围栏运营区域内,不能启用。", I18nKeyConstant.POINT_ENABLE_NOT_IN_OPERATING_AREA); } XpElectronicFenceArea electronicFenceArea = xpElectronicFenceAreaService.getById(electronicAreaId); if (electronicFenceArea == null || electronicFenceArea.getElectronicId() == null) { throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST); } if (xpPoint.getPointLon() == null || xpPoint.getPointLat() == null) { throw new ServiceException("系统异常", I18nKeyConstant.SYSTEM_EXCEPTION); } String fenceId = String.valueOf(electronicFenceArea.getElectronicId()); boolean inOperatingArea = xpGeoFencingPolygonService.isInFence(xpPoint.getPointLon(), xpPoint.getPointLat(), fenceId, "operating_area"); if (!inOperatingArea) { throw new ServiceException("当前点位不在围栏运营区域内,不能启用。", I18nKeyConstant.POINT_ENABLE_NOT_IN_OPERATING_AREA); } boolean inProhibitedArea = xpGeoFencingPolygonService.isInFence(xpPoint.getPointLon(), xpPoint.getPointLat(), fenceId, "prohibited_area"); if (inProhibitedArea) { throw new ServiceException("当前点位在禁行区内,不能启用。", I18nKeyConstant.POINT_ENABLE_IN_PROHIBITED_AREA); } } xpPoint.setFpElectronicAreaId(null); // 使用 lambdaUpdate 确保 pointPictures 为 null 时也能将数据库字段清空 // (updateById 默认策略 NOT_NULL 会跳过 null 值,导致删除图片后数据库仍保留旧图片) xpPointService.lambdaUpdate() .eq(XpPoint::getId, xpPoint.getId()) .set(XpPoint::getPointName, xpPoint.getPointName()) .set(XpPoint::getPointPoi, xpPoint.getPointPoi()) .set(XpPoint::getPointNumber, xpPoint.getPointNumber()) .set(XpPoint::getOperationStatus, xpPoint.getOperationStatus()) .set(XpPoint::getPointType, xpPoint.getPointType()) .set(XpPoint::getVerificationStatus, xpPoint.getVerificationStatus()) .set(XpPoint::getParkingTimeLimit, xpPoint.getParkingTimeLimit()) .set(XpPoint::getOperationType, xpPoint.getOperationType()) .set(XpPoint::getEffectivePeriod, xpPoint.getEffectivePeriod()) .set(XpPoint::getEffectiveTimePeriod, xpPoint.getEffectiveTimePeriod()) .set(XpPoint::getPointAddress, xpPoint.getPointAddress()) .set(XpPoint::getRemarks, xpPoint.getRemarks()) .set(XpPoint::getPointPictures, xpPoint.getPointPictures()) .set(XpPoint::getPointLon, xpPoint.getPointLon()) .set(XpPoint::getPointLat, xpPoint.getPointLat()) .set(XpPoint::getElectronicAreaId, xpPoint.getElectronicAreaId()) .set(XpPoint::getChargePoint, xpPoint.getChargePoint()) .set(XpPoint::getParkingPoint, xpPoint.getParkingPoint()) .set(XpPoint::getProvinceName, xpPoint.getProvinceName()) .set(XpPoint::getProvinceCode, xpPoint.getProvinceCode()) .set(XpPoint::getCityCode, xpPoint.getCityCode()) .set(XpPoint::getCityName, xpPoint.getCityName()) .set(XpPoint::getDistrictName, xpPoint.getDistrictName()) .set(XpPoint::getDistrictCode, xpPoint.getDistrictCode()) .update(); return R.ok(); } /** * 点位详情 */ @GetMapping("/api/v1/point/queryById") @ApiOperation(value = "点位详情") public R queryById(@RequestParam Long id) { return R.ok(xpPointService.getById(id)); } /** * 启用/禁用 */ @PostMapping("/enableDisable") @ApiOperation(value = "启用/禁用") public R enableDisable(@RequestBody XpPointEnableDisableDto dto) { XpPoint xpPoint = xpPointService.getById(dto.getId()); xpPoint.setOperationStatus(dto.getOperationStatus()); xpPoint.setVerificationStatus(dto.getVerificationStatus()); xpPoint.setFpElectronicAreaId(null); xpPointService.updateById(xpPoint); return R.ok(); } private static XpPointExportExcel toExportRow(XpPoint xpPoint) { XpPointExportExcel exportExcel = new XpPointExportExcel(); exportExcel.setPointNumber(xpPoint.getPointNumber()); exportExcel.setPointName(xpPoint.getPointName()); exportExcel.setStatus(calcExportStatus(xpPoint.getOperationStatus(), xpPoint.getVerificationStatus())); exportExcel.setParkingTimeLimit(xpPoint.getParkingTimeLimit() == null ? null : String.valueOf(xpPoint.getParkingTimeLimit())); exportExcel.setPointLon(xpPoint.getPointLon()); exportExcel.setPointLat(xpPoint.getPointLat()); exportExcel.setCreateBy(xpPoint.getCreateBy()); exportExcel.setCreateTime(formatExportTime(xpPoint.getCreateTime())); return exportExcel; } private static String formatExportTime(LocalDateTime time) { return time == null ? null : EXPORT_TIME_FORMATTER.format(time); } private static Integer calcExportStatus(Integer operationStatus, Integer verificationStatus) { if (operationStatus == null) { return null; } if (verificationStatus == 1) { return 1; } if (verificationStatus == 2) { return 2; } if (verificationStatus == 4) { return 3; } if (verificationStatus == 3 && operationStatus == 1) { return 4; } if (verificationStatus == 5 && operationStatus == 1) { return 5; } if (operationStatus == 2) { return 6; } if (operationStatus == 3) { return 7; } if (operationStatus == 4) { return 8; } if (operationStatus != 1 || verificationStatus == null) { return null; } return null; } }