feat(ai-report): 新增委托单位管理和报告审批功能

- 新增委托单位管理模块,支持增删改查、导入导出功能
- 实现委托单位 Excel 模板下载和数据导入功能
- 添加报告审批抽象处理器,支持审批流程日志记录
- 统一文件存储路径配置,将各模块独立路径改为统一根目录
- 新增 PQDIF 文件名存储字段,完善文件管理功能
- 修复代码中的乱码问题和错误提示信息
- 优化系统常量定义和字典配置管理
This commit is contained in:
2026-07-01 13:15:57 +08:00
parent 0ea686d3cb
commit c1ad7feec2
99 changed files with 6511 additions and 24 deletions

View File

@@ -11,6 +11,7 @@
</parent>
<artifactId>client-unit</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
@@ -30,5 +31,27 @@
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
<dependency>
<groupId>com.njcn.gather</groupId>
<artifactId>system</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
package com.njcn.gather.aireport.clientunit.component;
import cn.hutool.core.util.StrUtil;
import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitUserNameVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class ClientUnitUserNameResolver {
private final ClientUnitMapper clientUnitMapper;
public Map<String, String> resolveUserNames(Collection<String> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Collections.emptyMap();
}
Set<String> validIds = userIds.stream()
.filter(StrUtil::isNotBlank)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (validIds.isEmpty()) {
return Collections.emptyMap();
}
List<ClientUnitUserNameVO> users = clientUnitMapper.selectUserNamesByIds(validIds);
if (users == null || users.isEmpty()) {
return Collections.emptyMap();
}
return users.stream()
.filter(user -> user != null && StrUtil.isNotBlank(user.getId()) && StrUtil.isNotBlank(user.getName()))
.collect(Collectors.toMap(ClientUnitUserNameVO::getId, ClientUnitUserNameVO::getName, (first, second) -> first));
}
}

View File

@@ -0,0 +1,112 @@
package com.njcn.gather.aireport.clientunit.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.gather.aireport.clientunit.pojo.param.ClientUnitParam;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitImportResultVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitVO;
import com.njcn.gather.aireport.clientunit.service.IClientUnitService;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 委托单位管理接口。
*/
@Api(tags = "委托单位管理")
@RestController
@RequestMapping("/clientUnit")
@RequiredArgsConstructor
public class ClientUnitController extends BaseController {
private final IClientUnitService clientUnitService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询委托单位列表")
@PostMapping("/list")
public HttpResult<Page<ClientUnitVO>> list(@RequestBody(required = false) ClientUnitParam.QueryParam param) {
String methodDescribe = getMethodDescribe("list");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
clientUnitService.listClientUnits(param), methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@ApiOperation("新增委托单位")
@PostMapping("/add")
public HttpResult<Boolean> add(@RequestBody @Validated ClientUnitParam.AddParam param) {
String methodDescribe = getMethodDescribe("add");
boolean result = clientUnitService.addClientUnit(param);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@ApiOperation("编辑委托单位")
@PostMapping("/update")
public HttpResult<Boolean> update(@RequestBody @Validated ClientUnitParam.UpdateParam param) {
String methodDescribe = getMethodDescribe("update");
boolean result = clientUnitService.updateClientUnit(param);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询委托单位详情")
@ApiImplicitParam(name = "id", value = "委托单位ID", required = true)
@GetMapping("/getById")
public HttpResult<ClientUnitVO> getById(@RequestParam("id") String id) {
String methodDescribe = getMethodDescribe("getById");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
clientUnitService.getClientUnit(id), methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
@ApiOperation("删除委托单位")
@PostMapping("/delete")
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
boolean result = clientUnitService.deleteClientUnits(ids);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("导出委托单位")
@PostMapping("/export")
public void export(@RequestBody(required = false) ClientUnitParam.QueryParam param) {
clientUnitService.exportClientUnits(param);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("下载委托单位导入模板")
@GetMapping("/importTemplate")
public void importTemplate() {
clientUnitService.downloadImportTemplate();
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@ApiOperation("导入委托单位")
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
public HttpResult<ClientUnitImportResultVO> importExcel(@RequestParam("file") MultipartFile file) {
String methodDescribe = getMethodDescribe("importExcel");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
clientUnitService.importClientUnits(file), methodDescribe);
}
}

View File

@@ -0,0 +1,25 @@
package com.njcn.gather.aireport.clientunit.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitUserNameVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@Mapper
public interface ClientUnitMapper extends BaseMapper<ClientUnitPO> {
@Select({
"<script>",
"select id, name from sys_user where id in",
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>",
"#{id}",
"</foreach>",
"</script>"
})
List<ClientUnitUserNameVO> selectUserNamesByIds(@Param("ids") Collection<String> ids);
}

View File

@@ -0,0 +1,13 @@
package com.njcn.gather.aireport.clientunit.pojo.constant;
/**
* 委托单位业务常量。
*/
public final class ClientUnitConst {
public static final int STATUS_DELETED = 0;
public static final int STATUS_NORMAL = 1;
private ClientUnitConst() {
}
}

View File

@@ -0,0 +1,55 @@
package com.njcn.gather.aireport.clientunit.pojo.param;
import com.njcn.web.pojo.param.BaseParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* 委托单位请求参数。
*/
public class ClientUnitParam {
@Data
@EqualsAndHashCode(callSuper = true)
public static class QueryParam extends BaseParam {
@ApiModelProperty("单位名称关键字")
private String name;
@ApiModelProperty("创建开始时间yyyy-MM-dd HH:mm:ss")
private String searchBeginTime;
@ApiModelProperty("创建结束时间yyyy-MM-dd HH:mm:ss")
private String searchEndTime;
}
@Data
public static class AddParam {
@ApiModelProperty("单位名称")
@NotBlank(message = "单位名称不能为空")
@Size(max = 32, message = "单位名称不能超过32个字符")
private String name;
@ApiModelProperty("联系人")
@Size(max = 200, message = "联系人不能超过200个字符")
private String contactName;
@ApiModelProperty("联系方式")
@Size(max = 32, message = "联系方式不能超过32个字符")
private String phonenumber;
@ApiModelProperty("地址")
@Size(max = 128, message = "地址不能超过128个字符")
private String address;
}
@Data
public static class UpdateParam extends AddParam {
@ApiModelProperty("委托单位ID")
@NotBlank(message = "委托单位ID不能为空")
private String id;
}
}

View File

@@ -0,0 +1,39 @@
package com.njcn.gather.aireport.clientunit.pojo.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 委托单位记录。
*/
@Data
@TableName("ai_clientunit")
public class ClientUnitPO implements Serializable {
private static final long serialVersionUID = 2517699918054153742L;
@TableId("id")
private String id;
@TableField("name")
private String name;
@TableField("contact_name")
private String contactName;
@TableField("phonenumber")
private String phonenumber;
@TableField("address")
private String address;
@TableField("status")
private Integer status;
@TableField("create_by")
private String createBy;
@TableField("create_time")
private LocalDateTime createTime;
@TableField("update_by")
private String updateBy;
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.gather.aireport.clientunit.pojo.vo;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
/**
* 委托单位 Excel 行对象。
*/
@Data
public class ClientUnitExcelVO implements Serializable {
private static final long serialVersionUID = 8533457988358235671L;
@Excel(name = "单位名称", width = 25)
private String name;
@Excel(name = "联系人", width = 20)
private String contactName;
@Excel(name = "联系方式", width = 20)
private String phonenumber;
@Excel(name = "地址", width = 35)
private String address;
}

View File

@@ -0,0 +1,19 @@
package com.njcn.gather.aireport.clientunit.pojo.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 委托单位导入结果。
*/
@Data
public class ClientUnitImportResultVO implements Serializable {
private static final long serialVersionUID = 509902603493571405L;
private int successCount;
private int failCount;
private List<String> failReasons = new ArrayList<String>();
}

View File

@@ -0,0 +1,9 @@
package com.njcn.gather.aireport.clientunit.pojo.vo;
import lombok.Data;
@Data
public class ClientUnitUserNameVO {
private String id;
private String name;
}

View File

@@ -0,0 +1,69 @@
package com.njcn.gather.aireport.clientunit.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 委托单位展示对象。
*/
@Data
public class ClientUnitVO implements Serializable {
private static final long serialVersionUID = 8924649462626990262L;
private static final String DEFAULT_EMPTY_DISPLAY = "-";
private String id;
private String name;
private String contactName;
private String phonenumber;
private String address;
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
private String updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime updateTime;
public String getId() {
return defaultDisplay(id);
}
public String getName() {
return defaultDisplay(name);
}
public String getContactName() {
return defaultDisplay(contactName);
}
public String getPhonenumber() {
return defaultDisplay(phonenumber);
}
public String getAddress() {
return defaultDisplay(address);
}
public String getCreateBy() {
return defaultDisplay(createBy);
}
public String getUpdateBy() {
return defaultDisplay(updateBy);
}
private String defaultDisplay(String value) {
return StringUtils.hasText(value) ? value : DEFAULT_EMPTY_DISPLAY;
}
}

View File

@@ -0,0 +1,30 @@
package com.njcn.gather.aireport.clientunit.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.aireport.clientunit.pojo.param.ClientUnitParam;
import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitImportResultVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface IClientUnitService extends IService<ClientUnitPO> {
Page<ClientUnitVO> listClientUnits(ClientUnitParam.QueryParam param);
boolean addClientUnit(ClientUnitParam.AddParam param);
boolean updateClientUnit(ClientUnitParam.UpdateParam param);
ClientUnitVO getClientUnit(String id);
boolean deleteClientUnits(List<String> ids);
void exportClientUnits(ClientUnitParam.QueryParam param);
void downloadImportTemplate();
ClientUnitImportResultVO importClientUnits(MultipartFile file);
}

View File

@@ -0,0 +1,437 @@
package com.njcn.gather.aireport.clientunit.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.clientunit.component.ClientUnitUserNameResolver;
import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper;
import com.njcn.gather.aireport.clientunit.pojo.constant.ClientUnitConst;
import com.njcn.gather.aireport.clientunit.pojo.param.ClientUnitParam;
import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitExcelVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitImportResultVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitVO;
import com.njcn.gather.aireport.clientunit.service.IClientUnitService;
import com.njcn.gather.system.util.ExportFileNameUtil;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.ExcelUtil;
import com.njcn.web.utils.HttpServletUtil;
import com.njcn.web.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 委托单位业务实现。
*/
@Slf4j
@Service
public class ClientUnitServiceImpl extends ServiceImpl<ClientUnitMapper, ClientUnitPO> implements IClientUnitService {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final String[] TEMPLATE_HEADERS = new String[]{"单位名称", "联系人", "联系方式", "地址"};
private final ClientUnitUserNameResolver clientUnitUserNameResolver;
public ClientUnitServiceImpl(ClientUnitUserNameResolver clientUnitUserNameResolver) {
this.clientUnitUserNameResolver = clientUnitUserNameResolver;
}
@Override
public Page<ClientUnitVO> listClientUnits(ClientUnitParam.QueryParam param) {
ClientUnitParam.QueryParam query = param == null ? new ClientUnitParam.QueryParam() : param;
Page<ClientUnitPO> page = this.page(
new Page<ClientUnitPO>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)),
buildQueryWrapper(query));
Page<ClientUnitVO> result = new Page<ClientUnitVO>(page.getCurrent(), page.getSize(), page.getTotal());
Map<String, String> userNameMap = resolveUserNameMap(page.getRecords());
result.setRecords(page.getRecords().stream()
.map(clientUnit -> toVO(clientUnit, userNameMap))
.collect(Collectors.toList()));
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addClientUnit(ClientUnitParam.AddParam param) {
NormalizedClientUnit data = normalizeParam(param);
LocalDateTime now = LocalDateTime.now();
String userId = resolveCurrentUserId();
ClientUnitPO clientUnit = new ClientUnitPO();
clientUnit.setId(UUID.randomUUID().toString());
clientUnit.setName(data.getName());
clientUnit.setContactName(data.getContactName());
clientUnit.setPhonenumber(data.getPhonenumber());
clientUnit.setAddress(data.getAddress());
clientUnit.setStatus(ClientUnitConst.STATUS_NORMAL);
clientUnit.setCreateBy(userId);
clientUnit.setCreateTime(now);
clientUnit.setUpdateBy(userId);
clientUnit.setUpdateTime(now);
return this.save(clientUnit);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateClientUnit(ClientUnitParam.UpdateParam param) {
if (param == null || StrUtil.isBlank(param.getId())) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
}
ClientUnitPO clientUnit = requireNormal(param.getId());
NormalizedClientUnit data = normalizeParam(param);
checkDuplicate(data.getName(), clientUnit.getId());
clientUnit.setName(data.getName());
clientUnit.setContactName(data.getContactName());
clientUnit.setPhonenumber(data.getPhonenumber());
clientUnit.setAddress(data.getAddress());
clientUnit.setUpdateBy(resolveCurrentUserId());
clientUnit.setUpdateTime(LocalDateTime.now());
return this.updateById(clientUnit);
}
@Override
public ClientUnitVO getClientUnit(String id) {
ClientUnitPO clientUnit = requireNormal(id);
return toVO(clientUnit, resolveUserNameMap(Arrays.asList(clientUnit)));
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteClientUnits(List<String> ids) {
List<String> validIds = normalizeIds(ids);
if (validIds.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
}
return this.lambdaUpdate()
.set(ClientUnitPO::getStatus, ClientUnitConst.STATUS_DELETED)
.set(ClientUnitPO::getUpdateBy, resolveCurrentUserId())
.set(ClientUnitPO::getUpdateTime, LocalDateTime.now())
.in(ClientUnitPO::getId, validIds)
.eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
.update();
}
@Override
public void exportClientUnits(ClientUnitParam.QueryParam param) {
List<ClientUnitExcelVO> exportRows = this.list(buildQueryWrapper(param == null ? new ClientUnitParam.QueryParam() : param))
.stream()
.map(this::toExcelVO)
.collect(Collectors.toList());
ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("委托单位列表.xlsx"), "委托单位列表", ClientUnitExcelVO.class, exportRows);
}
@Override
public void downloadImportTemplate() {
HttpServletResponse response = HttpServletUtil.getResponse();
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("委托单位导入模板");
writeTemplateHeader(workbook, sheet);
String fileName = URLEncoder.encode(ExportFileNameUtil.appendToday("委托单位导入模板.xlsx"), "UTF-8");
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
try (ServletOutputStream outputStream = response.getOutputStream()) {
workbook.write(outputStream);
outputStream.flush();
}
} catch (IOException exception) {
log.error("下载委托单位导入模板失败", exception);
throw new BusinessException(CommonResponseEnum.FAIL, "下载委托单位导入模板失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public ClientUnitImportResultVO importClientUnits(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, "导入文件不能为空");
}
List<ClientUnitExcelVO> rows = readImportRows(file);
ClientUnitImportResultVO result = new ClientUnitImportResultVO();
if (rows.isEmpty()) {
result.setFailCount(0);
result.setSuccessCount(0);
return result;
}
LocalDateTime now = LocalDateTime.now();
String userId = resolveCurrentUserId();
Set<String> fileKeys = new HashSet<String>();
List<ClientUnitPO> saveRows = new ArrayList<ClientUnitPO>();
for (int i = 0; i < rows.size(); i++) {
int rowNumber = i + 2;
try {
NormalizedClientUnit data = normalizeExcelRow(rows.get(i));
String duplicateKey = data.getName();
if (!fileKeys.add(duplicateKey)) {
throw new BusinessException(CommonResponseEnum.FAIL, "文件内存在重复委托单位名称");
}
checkDuplicate(data.getName(), null);
ClientUnitPO clientUnit = new ClientUnitPO();
clientUnit.setId(UUID.randomUUID().toString());
clientUnit.setName(data.getName());
clientUnit.setContactName(data.getContactName());
clientUnit.setPhonenumber(data.getPhonenumber());
clientUnit.setAddress(data.getAddress());
clientUnit.setStatus(ClientUnitConst.STATUS_NORMAL);
clientUnit.setCreateBy(userId);
clientUnit.setCreateTime(now);
clientUnit.setUpdateBy(userId);
clientUnit.setUpdateTime(now);
saveRows.add(clientUnit);
result.setSuccessCount(result.getSuccessCount() + 1);
} catch (BusinessException exception) {
result.setFailCount(result.getFailCount() + 1);
result.getFailReasons().add("" + rowNumber + "行:" + exception.getMessage());
}
}
if (!saveRows.isEmpty()) {
this.saveBatch(saveRows);
}
return result;
}
private LambdaQueryWrapper<ClientUnitPO> buildQueryWrapper(ClientUnitParam.QueryParam query) {
LambdaQueryWrapper<ClientUnitPO> wrapper = new LambdaQueryWrapper<ClientUnitPO>();
wrapper.eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
.like(StrUtil.isNotBlank(query.getName()), ClientUnitPO::getName, query.getName());
if (StrUtil.isNotBlank(query.getSearchBeginTime())) {
wrapper.ge(ClientUnitPO::getCreateTime, parseDateTime(query.getSearchBeginTime(), "开始时间格式不正确"));
}
if (StrUtil.isNotBlank(query.getSearchEndTime())) {
wrapper.le(ClientUnitPO::getCreateTime, parseDateTime(query.getSearchEndTime(), "结束时间格式不正确"));
}
wrapper.orderByDesc(ClientUnitPO::getCreateTime);
return wrapper;
}
private ClientUnitPO requireNormal(String id) {
String normalizedId = trimToNull(id);
if (normalizedId == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
}
ClientUnitPO clientUnit = this.lambdaQuery()
.eq(ClientUnitPO::getId, normalizedId)
.eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
.one();
if (clientUnit == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位不存在或已删除");
}
return clientUnit;
}
private void checkDuplicate(String name, String excludeId) {
LambdaQueryWrapper<ClientUnitPO> wrapper = new LambdaQueryWrapper<ClientUnitPO>();
wrapper.eq(ClientUnitPO::getName, name)
.eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
.ne(StrUtil.isNotBlank(excludeId), ClientUnitPO::getId, excludeId);
if (this.count(wrapper) > 0) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位名称已存在");
}
}
private NormalizedClientUnit normalizeParam(ClientUnitParam.AddParam param) {
if (param == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "委托单位参数不能为空");
}
return normalizeFields(param.getName(), param.getContactName(), param.getPhonenumber(), param.getAddress());
}
private NormalizedClientUnit normalizeExcelRow(ClientUnitExcelVO row) {
if (row == null) {
throw new BusinessException(CommonResponseEnum.FAIL, "空行");
}
return normalizeFields(row.getName(), row.getContactName(), row.getPhonenumber(), row.getAddress());
}
private NormalizedClientUnit normalizeFields(String name, String contactName, String phonenumber, String address) {
NormalizedClientUnit data = new NormalizedClientUnit();
data.setName(requireText(name, "单位名称不能为空", 32, "单位名称不能超过32个字符"));
data.setContactName(optionalText(contactName, 200, "联系人不能超过200个字符"));
data.setPhonenumber(optionalText(phonenumber, 32, "联系方式不能超过32个字符"));
data.setAddress(optionalText(address, 128, "地址不能超过128个字符"));
return data;
}
private String requireText(String value, String blankMessage, int maxLength, String lengthMessage) {
String text = trimToNull(value);
if (text == null) {
throw new BusinessException(CommonResponseEnum.FAIL, blankMessage);
}
if (text.length() > maxLength) {
throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
}
return text;
}
private String optionalText(String value, int maxLength, String lengthMessage) {
String text = trimToNull(value);
if (text != null && text.length() > maxLength) {
throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
}
return text;
}
private List<ClientUnitExcelVO> readImportRows(MultipartFile file) {
try (InputStream inputStream = file.getInputStream();
XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
XSSFSheet sheet = workbook.getNumberOfSheets() == 0 ? null : workbook.getSheetAt(0);
if (sheet == null || sheet.getLastRowNum() < 1) {
return Collections.emptyList();
}
List<ClientUnitExcelVO> rows = new ArrayList<ClientUnitExcelVO>();
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (isEmptyRow(row)) {
continue;
}
ClientUnitExcelVO excelVO = new ClientUnitExcelVO();
excelVO.setName(readCell(row, 0));
excelVO.setContactName(readCell(row, 1));
excelVO.setPhonenumber(readCell(row, 2));
excelVO.setAddress(readCell(row, 3));
rows.add(excelVO);
}
return rows;
} catch (IOException exception) {
log.error("读取委托单位导入文件失败", exception);
throw new BusinessException(CommonResponseEnum.FAIL, "读取导入文件失败");
}
}
private void writeTemplateHeader(XSSFWorkbook workbook, XSSFSheet sheet) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
Row row = sheet.createRow(0);
for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
Cell cell = row.createCell(i);
cell.setCellValue(TEMPLATE_HEADERS[i]);
cell.setCellStyle(style);
sheet.setColumnWidth(i, 25 * 256);
}
}
private boolean isEmptyRow(Row row) {
if (row == null) {
return true;
}
for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
if (trimToNull(readCell(row, i)) != null) {
return false;
}
}
return true;
}
private String readCell(Row row, int index) {
Cell cell = row.getCell(index);
if (cell == null) {
return null;
}
return trimToNull(new DataFormatter().formatCellValue(cell));
}
private List<String> normalizeIds(List<String> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<String>();
for (String id : ids) {
String normalizedId = trimToNull(id);
if (normalizedId != null && !result.contains(normalizedId)) {
result.add(normalizedId);
}
}
return result;
}
private LocalDateTime parseDateTime(String value, String errorMessage) {
try {
return LocalDateTime.parse(value.trim(), DATE_TIME_FORMATTER);
} catch (DateTimeParseException exception) {
throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
}
}
private ClientUnitVO toVO(ClientUnitPO clientUnit, Map<String, String> userNameMap) {
ClientUnitVO vo = new ClientUnitVO();
BeanUtil.copyProperties(clientUnit, vo);
vo.setCreateBy(resolveUserName(clientUnit.getCreateBy(), userNameMap));
vo.setUpdateBy(resolveUserName(clientUnit.getUpdateBy(), userNameMap));
return vo;
}
private ClientUnitExcelVO toExcelVO(ClientUnitPO clientUnit) {
ClientUnitExcelVO vo = new ClientUnitExcelVO();
BeanUtil.copyProperties(clientUnit, vo);
return vo;
}
private Map<String, String> resolveUserNameMap(List<ClientUnitPO> clientUnits) {
List<String> userIds = clientUnits.stream()
.flatMap(clientUnit -> Arrays.asList(clientUnit.getCreateBy(), clientUnit.getUpdateBy()).stream())
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
return clientUnitUserNameResolver.resolveUserNames(userIds);
}
private String resolveUserName(String userId, Map<String, String> userNameMap) {
if (StrUtil.isBlank(userId)) {
return null;
}
String userName = userNameMap == null ? null : userNameMap.get(userId);
return StrUtil.isNotBlank(userName) ? userName : userId;
}
private String resolveCurrentUserId() {
String userId = RequestUtil.getUserId();
return StrUtil.isBlank(userId) ? null : userId;
}
private String trimToNull(String value) {
if (value == null) {
return null;
}
String text = value.trim();
return text.isEmpty() ? null : text;
}
@lombok.Data
private static class NormalizedClientUnit {
private String name;
private String contactName;
private String phonenumber;
private String address;
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper">
</mapper>