package com.rj.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.rj.common.AudioManagementConstants; import com.rj.common.DataEnrichmentUtil; import com.rj.dto.CustomerPhotoPreviewResult; import com.rj.dto.CustomerPhotoUploadResult; import com.rj.dto.CustomerStageUpdateResult; import com.rj.dto.NearbyMerchantSearchResult; import com.rj.entity.AudioManagement; import com.rj.entity.CustomerManagement; import com.rj.entity.SalesManagement; import com.rj.service.IAudioManagementService; import com.rj.service.ICustomerManagementService; import com.rj.service.INearbyMerchantSearchService; import com.rj.service.ISalesManagementService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** *

* 客户管理表 前端控制器 *

* * @author 李中华 ,spllzh * @since 2025-08-07 */ @RestController @RequestMapping("/api/customerManagement") @Tag(name = "客户管理", description = "客户管理相关接口") public class CustomerManagementController { @Autowired private ICustomerManagementService customerManagementService; @Autowired private IAudioManagementService audioManagementService; @Autowired private ISalesManagementService salesManagementService; @Autowired private INearbyMerchantSearchService nearbyMerchantSearchService; /** * 新增客户 */ @PostMapping("/add") @Operation(summary = "新增客户", description = "添加新的客户信息") @Transactional(rollbackFor = Exception.class) public ResponseEntity> addCustomer( @Parameter(description = "客户信息", required = true) @RequestBody CustomerManagement customerManagement) { Map result = new HashMap<>(); try { boolean isUpdate = customerManagement.getId() != null && !customerManagement.getId().trim().isEmpty(); LocalDateTime now = LocalDateTime.now(); String operationType = customerManagement.getOperationType(); String salesPhone = customerManagement.getSalesPhone(); if (isUpdate) { CustomerManagement existing = customerManagementService.getById(customerManagement.getId()); if (existing == null) { result.put("success", false); result.put("message", "客户不存在,无法更新"); return ResponseEntity.badRequest().body(result); } // customerManagement.setCreateTime(existing.getCreateTime()); customerManagement.setUpdateTime(now); customerManagement.setRecordingCount(customerManagement.getRecordingCount()+1); } else { // 根据salesPhone查询销售管理表,获取经销商信息 if (salesPhone != null && !salesPhone.trim().isEmpty()) { LambdaQueryWrapper salesQueryWrapper = new LambdaQueryWrapper<>(); salesQueryWrapper.eq(SalesManagement::getLoginAccount, salesPhone.trim()); List salesList = salesManagementService.list(salesQueryWrapper); if (salesList != null && !salesList.isEmpty()) { SalesManagement sales = salesList.get(0); customerManagement.setDealershipId(sales.getDealershipId()); customerManagement.setDealershipName(sales.getDealershipName()); // 同时设置销售ID和销售名称 if (customerManagement.getSalesId() == null || customerManagement.getSalesId().trim().isEmpty()) { customerManagement.setSalesId(sales.getId()); } if (customerManagement.getSalesName() == null || customerManagement.getSalesName().trim().isEmpty()) { customerManagement.setSalesName(sales.getSalesName()); } } } customerManagement.setCreateTime(now); customerManagement.setUpdateTime(now); customerManagement.setRecordingCount(1); } boolean success = customerManagementService.saveOrUpdate(customerManagement); if (success) { if ("start".equalsIgnoreCase(operationType)) { if (customerManagement.getId() == null || customerManagement.getId().trim().isEmpty()) { throw new IllegalStateException("客户ID为空,无法同步录音记录"); } // 检查销售人员是否正在服务其他客户 if (salesPhone != null && !salesPhone.trim().isEmpty()) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AudioManagement::getSalesPhone, salesPhone.trim()) .eq(AudioManagement::getServiceStatus, AudioManagementConstants.SERVICE_STATUS_IN_SERVICE); long count = audioManagementService.count(queryWrapper); if (count > 0) { //如果正在服务,就直接返回,不能新增记录 result.put("success", true); result.put("message", isUpdate ? "客户信息更新成功" : "客户添加成功"); result.put("message", "该销售人员正在服务其他客户,无法创建新的服务记录"); return ResponseEntity.badRequest().body(result); //如果正在服务,就直接返回,不能新增记录 } } AudioManagement audioRecord = buildAudioRecordFromCustomer(customerManagement, now); boolean audioSaved = audioManagementService.save(audioRecord); if (!audioSaved) { throw new IllegalStateException("录音管理数据保存失败"); } result.put("audioRecordId", audioRecord.getId()); } result.put("success", true); result.put("message", isUpdate ? "客户信息更新成功" : "客户添加成功"); result.put("data", customerManagement); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", isUpdate ? "客户信息更新失败" : "客户添加失败"); return ResponseEntity.badRequest().body(result); } } catch (Exception e) { result.put("success", false); result.put("message", "客户添加异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 根据ID查询客户 */ @GetMapping("/get/{id}") @Operation(summary = "根据ID查询客户", description = "根据客户ID获取客户详细信息") public ResponseEntity> getCustomerById( @Parameter(description = "客户ID", required = true) @PathVariable String id) { Map result = new HashMap<>(); try { CustomerManagement customer = customerManagementService.getById(id); if (customer != null) { result.put("success", true); result.put("message", "查询成功"); result.put("data", customer); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", "客户不存在"); return ResponseEntity.notFound().build(); } } catch (Exception e) { result.put("success", false); result.put("message", "查询异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 根据联系方式查询客户 */ @GetMapping("/getByContact") @Operation(summary = "根据联系方式查询客户", description = "根据客户联系方式查询客户信息") public ResponseEntity> getCustomerByContact( @Parameter(description = "联系方式", required = true) @RequestParam String contact) { Map result = new HashMap<>(); try { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (contact != null && !contact.trim().isEmpty()) { queryWrapper.like(CustomerManagement::getContact, contact.trim()); } List customers = customerManagementService.list(queryWrapper); result.put("success", true); result.put("message", "查询成功"); result.put("data", customers); result.put("count", customers.size()); return ResponseEntity.ok(result); } catch (Exception e) { result.put("success", false); result.put("message", "查询异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } @Autowired private DataEnrichmentUtil dataEnrichmentUtil; /** * 根据城市与地址做地理编码,再按半径搜索周边商家(高德 place/around,逻辑同 AllShopCollectorV5)。 */ @GetMapping("/searchNearbyMerchants") @Operation(summary = "附近商家检索", description = "根据 CITY、ADDRESS 解析坐标,按 RADIUS、MAX_PAGE、maxRecordsOfpage 调用高德周边搜索") public ResponseEntity> searchNearbyMerchants( @Parameter(description = "城市(传给高德地理编码 city)", required = true) @RequestParam("CITY") String city, @Parameter(description = "地址/地标(传给高德地理编码 address)", required = true) @RequestParam("ADDRESS") String address, @Parameter(description = "搜索半径(米)", required = true) @RequestParam("RADIUS") int radius, @Parameter(description = "最大请求页数", required = true) @RequestParam("MAX_PAGE") int maxPage, @Parameter(description = "每页条数(1~50,高德 offset)", required = true) @RequestParam("maxRecordsOfpage") int maxRecordsOfpage) { NearbyMerchantSearchResult search = nearbyMerchantSearchService.searchNearbyMerchants( city, address, radius, maxPage, maxRecordsOfpage); Map result = new HashMap<>(); result.put("success", search.isSuccess()); result.put("message", search.getMessage()); if (search.isSuccess()) { result.put("centerLocation", search.getCenterLocation()); result.put("totalCount", search.getTotalCount()); result.put("data", search.getMerchants()); result.put("detailedReport", search.getDetailedReport()); result.put("customerInsertedCount", search.getCustomerInsertedCount()); } return ResponseEntity.status(search.getHttpStatus()).body(result); } /** * 分页查询客户列表 */ @GetMapping("/list") @Operation(summary = "分页查询客户列表", description = "分页查询客户信息列表") public ResponseEntity> getCustomerList( @Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") Integer current, @Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") Integer size, @Parameter(description = "客户姓名(模糊查询)")@RequestParam(required = false) String customerName, @Parameter(description = "所属人姓名(模糊查询)")@RequestParam(required = false) String salesName, @Parameter(description = "所属人电话")@RequestParam(required = false) String salesPhone, @Parameter(description = "联系方式(模糊查询)") @RequestParam(required = false) String contact, @Parameter(description = "联系方式(模糊查询)") @RequestParam(required = false) String stage ) { Map result = new HashMap<>(); try { Page page = new Page<>(current, size); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); // 添加查询条件 if (customerName != null && !customerName.trim().isEmpty()) { queryWrapper.like(CustomerManagement::getCustomerName, customerName); } if (salesPhone != null && !salesPhone.trim().isEmpty()) { queryWrapper.like(CustomerManagement::getSalesPhone, salesPhone); } if (contact != null && !contact.trim().isEmpty()) { queryWrapper.like(CustomerManagement::getContact, contact); } if (stage != null && !stage.trim().isEmpty()) { queryWrapper.eq(CustomerManagement::getStage, stage); } // 按创建时间倒序排列 queryWrapper.orderByDesc(CustomerManagement::getUpdateTime); Page customerPage = customerManagementService.page(page, queryWrapper); // 使用工具类提取ID列表 List dealershipIds = DataEnrichmentUtil.extractIds(customerPage.getRecords(), CustomerManagement::getDealershipId); List saleIds = DataEnrichmentUtil.extractIds(customerPage.getRecords(), CustomerManagement::getSalesId); // 使用工具类批量获取关联数据 Map dealershipNameMap = dataEnrichmentUtil.getDealershipIdNameMap(dealershipIds); Map saleNameMap = dataEnrichmentUtil.getSalesIdNameMap(saleIds); // 填充关联数据 customerPage.getRecords().forEach(customer -> { customer.setDealershipName(dealershipNameMap.get(customer.getDealershipId())); customer.setSalesName(saleNameMap.get(customer.getSalesId())); }); result.put("success", true); result.put("message", "查询成功"); result.put("data", customerPage.getRecords()); result.put("total", customerPage.getTotal()); result.put("current", customerPage.getCurrent()); result.put("size", customerPage.getSize()); result.put("pages", customerPage.getPages()); return ResponseEntity.ok(result); } catch (Exception e) { result.put("success", false); result.put("message", "查询异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 更新客户信息 */ @PutMapping("/update") @Operation(summary = "更新客户信息", description = "更新客户详细信息") public ResponseEntity> updateCustomer( @Parameter(description = "客户信息", required = true) @RequestBody CustomerManagement customerManagement) { Map result = new HashMap<>(); try { if (customerManagement.getId() == null || customerManagement.getId().trim().isEmpty()) { result.put("success", false); result.put("message", "客户ID不能为空"); return ResponseEntity.badRequest().body(result); } customerManagement.setUpdateTime(LocalDateTime.now()); boolean success = customerManagementService.updateById(customerManagement); if (success) { result.put("success", true); result.put("message", "客户信息更新成功"); result.put("data", customerManagement); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", "客户信息更新失败"); return ResponseEntity.badRequest().body(result); } } catch (Exception e) { result.put("success", false); result.put("message", "更新异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 根据客户 ID 更新阶段(stage) */ @PatchMapping("/updateStage") @Operation(summary = "更新客户阶段", description = "根据客户 ID 更新 stage 字段") public ResponseEntity> updateStage( @Parameter(description = "客户ID", required = true) @RequestParam String id, @Parameter(description = "阶段", required = true) @RequestParam String stage) { CustomerStageUpdateResult updateResult = customerManagementService.updateCustomerStage(id, stage); Map result = new HashMap<>(); result.put("success", updateResult.isSuccess()); result.put("message", updateResult.getMessage()); if (updateResult.getCustomer() != null) { result.put("data", updateResult.getCustomer()); } return ResponseEntity.status(updateResult.getHttpStatus()).body(result); } /** * 根据ID删除客户 */ @DeleteMapping("/delete/{id}") @Operation(summary = "删除客户", description = "根据客户ID删除客户信息") public ResponseEntity> deleteCustomer( @Parameter(description = "客户ID", required = true) @PathVariable String id) { Map result = new HashMap<>(); try { boolean success = customerManagementService.removeById(id); if (success) { result.put("success", true); result.put("message", "客户删除成功"); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", "客户删除失败"); return ResponseEntity.badRequest().body(result); } } catch (Exception e) { result.put("success", false); result.put("message", "删除异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 批量删除客户 */ @DeleteMapping("/batchDelete") @Operation(summary = "批量删除客户", description = "根据客户ID列表批量删除客户信息") public ResponseEntity> batchDeleteCustomers( @Parameter(description = "客户ID列表", required = true) @RequestBody List ids) { Map result = new HashMap<>(); try { if (ids == null || ids.isEmpty()) { result.put("success", false); result.put("message", "客户ID列表不能为空"); return ResponseEntity.badRequest().body(result); } boolean success = customerManagementService.removeByIds(ids); if (success) { result.put("success", true); result.put("message", "批量删除成功,共删除 " + ids.size() + " 条记录"); return ResponseEntity.ok(result); } else { result.put("success", false); result.put("message", "批量删除失败"); return ResponseEntity.badRequest().body(result); } } catch (Exception e) { result.put("success", false); result.put("message", "批量删除异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 获取客户统计信息 */ @GetMapping("/statistics") @Operation(summary = "获取客户统计信息", description = "获取客户总数等统计信息") public ResponseEntity> getCustomerStatistics() { Map result = new HashMap<>(); try { long totalCount = customerManagementService.count(); Map statistics = new HashMap<>(); statistics.put("totalCustomers", totalCount); result.put("success", true); result.put("message", "统计信息获取成功"); result.put("data", statistics); return ResponseEntity.ok(result); } catch (Exception e) { result.put("success", false); result.put("message", "统计信息获取异常:" + e.getMessage()); return ResponseEntity.internalServerError().body(result); } } /** * 客户照片上传至 MinIO,返回可访问 URL */ @PostMapping("/uploadPhoto") @Operation(summary = "上传客户照片", description = "根据客户ID上传照片到 MinIO,返回文件访问 URL") public ResponseEntity> uploadCustomerPhoto( @Parameter(description = "客户ID") @RequestParam String id, @Parameter(description = "照片文件", required = true) @RequestParam("file") MultipartFile file) { CustomerPhotoUploadResult uploadResult = customerManagementService.uploadCustomerPhoto(id, file); Map result = new HashMap<>(); result.put("success", uploadResult.isSuccess()); result.put("message", uploadResult.getMessage()); if (uploadResult.getUrl() != null) { result.put("url", uploadResult.getUrl()); } return ResponseEntity.status(uploadResult.getHttpStatus()).body(result); } /** * 客户照片预览:从 MinIO 拉流返回图片。 * 方式一:传 {@code url}(与上传接口返回的 MinIO 访问 URL 一致)。 * 方式二:传 {@code id} + {@code photoType}(front / side / life),读取客户表中对应照片 URL。 */ @GetMapping("/previewPhoto") @Operation(summary = "预览客户照片", description = "根据 MinIO URL 或客户ID+照片类型从 MinIO 读取并返回图片流,便于 img 标签 src 引用") public ResponseEntity previewCustomerPhoto( @Parameter(description = "MinIO 完整访问 URL(与 uploadPhoto 返回的 url 一致)") @RequestParam(required = false) String url, @Parameter(description = "客户ID,与 photoType 联用") @RequestParam(required = false) String id, @Parameter(description = "照片类型:front 正面 / side 侧面 / life 生活照", example = "front") @RequestParam(required = false) String photoType) { CustomerPhotoPreviewResult preview = customerManagementService.openCustomerPhotoPreview(url, id, photoType); if (preview.getStatus() == CustomerPhotoPreviewResult.Status.NOT_FOUND) { return ResponseEntity.notFound().build(); } if (preview.getStatus() == CustomerPhotoPreviewResult.Status.BAD_REQUEST) { return ResponseEntity.badRequest().build(); } HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType(preview.getContentType())); headers.setCacheControl("private, max-age=3600"); Resource resource = new InputStreamResource(preview.getInputStream()); return ResponseEntity.ok().headers(headers).body(resource); } private AudioManagement buildAudioRecordFromCustomer(CustomerManagement customer, LocalDateTime now) { AudioManagement audio = new AudioManagement(); audio.setId(UUID.randomUUID().toString()); String recordingName = customer.getCustomerName() != null && !customer.getCustomerName().trim().isEmpty() ? customer.getCustomerName() + "的接待记录" : "客户接待记录"; audio.setRecordingName(recordingName); audio.setRecordingTime(now); audio.setUploadTime(now); audio.setCreateTime(now); audio.setUpdateTime(now); audio.setCustomerId(customer.getId()); audio.setCustomerName(customer.getCustomerName()); audio.setCustomerPhone(customer.getContact()); audio.setSalesId(customer.getSalesId()); audio.setSalesName(customer.getSalesName()); audio.setSalesPhone(customer.getSalesPhone()); audio.setDealershipId(customer.getDealershipId()); audio.setDealershipName(customer.getDealershipName()); audio.setIntentionLevel(customer.getStage()); audio.setRemarks(customer.getDetailedAddress()); audio.setInfoCarddescription(customer.getInfoCard()); audio.setCompanyType(customer.getCustomerSource()); audio.setScenario(customer.getScenario()); audio.setServiceStatus(AudioManagementConstants.SERVICE_STATUS_IN_SERVICE); return audio; } }