Commit e7f5653cbd65c6cb10ea14b40072ca91c5fad58b

Authored by 王富生
1 parent f461431b

注释人才机

urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java
1   -package com.zteits.urbanops.module.garden.controller.admin.costfee;
2   -
3   -import com.zteits.urbanops.framework.common.pojo.CommonResult;
4   -import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
5   -import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.WaterFeeImport;
6   -import com.zteits.urbanops.module.garden.enums.TemplateEnum;
7   -import com.zteits.urbanops.module.garden.service.costfee.CostFeeService;
8   -import io.swagger.v3.oas.annotations.Operation;
9   -import io.swagger.v3.oas.annotations.tags.Tag;
10   -import jakarta.servlet.http.HttpServletResponse;
11   -import lombok.AllArgsConstructor;
12   -import lombok.Data;
13   -import lombok.extern.slf4j.Slf4j;
14   -import org.springframework.beans.factory.annotation.Autowired;
15   -import org.springframework.beans.factory.annotation.Value;
16   -import org.springframework.core.io.ClassPathResource;
17   -import org.springframework.util.CollectionUtils;
18   -import org.springframework.web.bind.annotation.*;
19   -import org.springframework.web.multipart.MultipartFile;
20   -
21   -import java.io.IOException;
22   -import java.io.InputStream;
23   -import java.io.OutputStream;
24   -import java.lang.reflect.Method;
25   -import java.net.URLEncoder;
26   -import java.nio.charset.StandardCharsets;
27   -import java.util.*;
28   -
29   -import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
30   -import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
31   -
32   -/**
33   - * 人机材公共处理控制器
34   - * 处理Excel导入预览、提交等功能
35   - */
36   -@Tag(name = "管理后台 - 人机材批量导入")
37   -@RestController
38   -@RequestMapping("/costfee")
39   -@Slf4j
40   -public class CostFeeController{
41   -
42   - @Autowired
43   - private CostFeeService costFeeService;
44   -
45   - // 静态资源服务器基础URL(从配置文件读取)
46   - @Value("${static.resource.server.url}")
47   - private String staticResourceServerUrl;
48   -
49   - @Value("${static.resource.server.enable}")
50   - private Boolean staticResourceServerenable;
51   -
52   - // Excel文件校验相关
53   - private static final List<String> ALLOWED_EXCEL_SUFFIX = Arrays.asList(".xlsx", ".xlsm");
54   - private static final String EXCEL_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
55   - private static final String EXCEL_MIME_TYPE_XLS = "application/vnd.ms-excel";
56   -
57   - /**
58   - * 导入模板下载
59   - */
60   - @GetMapping("/get-import-template")
61   - @Operation(summary = "获得导入用户模板")
62   - public void importTemplate(HttpServletResponse response,
63   - @RequestParam("templateName") String templateName) throws IOException {
64   - // 参数校验
65   - if (templateName == null || templateName.trim().isEmpty()) {
66   - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "模板名称不能为空");
67   - return;
68   - }
69   -
70   - // 模板类型校验
71   - TemplateEnum template = TemplateEnum.getByCode(templateName);
72   - if (template == null) {
73   - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未知的模板类型: " + templateName);
74   - return;
75   - }
76   -
77   - if (staticResourceServerenable) {
78   - // 构建完整的静态服务器URL(确保路径正确)
79   - String serverUrl = staticResourceServerUrl;
80   - if (serverUrl != null && !serverUrl.endsWith("/")) {
81   - serverUrl += "/"; // 确保URL以/结尾,避免拼接错误
82   - }
83   -
84   - String encodedFileName = URLEncoder.encode(template.getName().split("\\.")[0], StandardCharsets.UTF_8.name());
85   -
86   - String templateFileName = encodedFileName + "." + template.getName().split("\\.")[1];
87   -
88   - String templateUrl = serverUrl + templateFileName; // 包含templates目录
89   -
90   - // 方式1:重定向到静态资源服务器
91   - response.sendRedirect(templateUrl);
92   - } else {
93   - String templateFileName = templateName + "." + template.getName().split("\\.")[1];
94   -
95   - // 写入模板文件到响应
96   - writeTemplateToResponse(response, template.getName(), templateFileName);
97   - }
98   - }
99   -
100   -
101   - /**
102   - * Excel文件上传预览
103   - */
104   - @PostMapping("/preview")
105   - @Operation(summary = "文件上传")
106   - public CommonResult previewExcel(@RequestParam("file") MultipartFile file,
107   - @RequestParam("templateName") String templateName) {
108   - // 基础参数校验
109   - if (templateName == null || templateName.trim().isEmpty()) {
110   - return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
111   -
112   - }
113   - // 模板类型校验
114   - TemplateEnum template = TemplateEnum.getByCode(templateName);
115   - if (template == null) {
116   - return CommonResult.error(BAD_REQUEST, "未知的模板类型");
117   - }
118   - String suffix = template.getName().split("\\.")[1];
119   - // 文件有效性校验
120   - if (!isValidExcelFile(file)) {
121   - return CommonResult.error(BAD_REQUEST, "请上传有效的Excel文件");
122   - }
123   - //部门信息缓存,校验正确性使用
124   -
125   -
126   - // 解析Excel并获取数据
127   - ImportResult<?> result = parseExcelFile(file, template);
128   -
129   - // 需预览的数据存入Redis并返回批次ID
130   - if (result.isNeedPreview()) {
131   - if (CollectionUtils.isEmpty(result.getDataList())) {
132   - return CommonResult.error(BAD_REQUEST, "导入的Excel文件不能为空");
133   -
134   - }
135   - String batchId = costFeeService.saveDataToRedis(result.getDataList(), templateName);
136   - Map<String, Object> resultMap = new HashMap<>(2);
137   - resultMap.put("batchId", batchId);
138   - resultMap.put("totalCount", result.getDataList().size());
139   - resultMap.put("templateName",templateName);
140   - return success(resultMap);
141   - }
142   - // 无需预览的直接返回数据
143   - return success(result.getDataList());
144   - }
145   -
146   -
147   - /**
148   - * 分页获取预览数据
149   - */
150   - @GetMapping("/preview-data")
151   - @Operation(summary = "批量上传分页预览")
152   - public CommonResult getPreviewData(@RequestParam("batchId") String batchId,
153   - @RequestParam("templateName") String templateName,
154   - @RequestParam(value = "page", defaultValue = "1") int page,
155   - @RequestParam(value = "size", defaultValue = "100000") int size) {
156   -
157   -
158   - // 参数校验
159   - if (batchId == null || batchId.trim().isEmpty()) {
160   - return CommonResult.error(BAD_REQUEST, "批次ID不能为空");
161   - }
162   - if (templateName == null || templateName.trim().isEmpty()) {
163   - return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
164   -
165   - }
166   - if (TemplateEnum.getByCode(templateName) == null) {
167   - return CommonResult.error(BAD_REQUEST, "未知的模板类型");
168   -
169   - }
170   - try {
171   - //if (page < 1 || size < 1 || size > 100) { // 限制每页最大100条,避免过大
172   - // return AjaxResult.error("分页参数无效(page≥1,1≤size≤100)");
173   - //}
174   -
175   - // 模板类型校验
176   - TemplateEnum template = TemplateEnum.getByCode(templateName);
177   - Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch
178   -
179   - // 从Redis获取分页数据
180   - PageResult<?> pageResult = costFeeService.getPagedDataFromRedis(batchId, templateName, page, size, importClass);
181   - return CommonResult.success(pageResult);
182   - } catch (Exception e) {
183   - log.error("获取预览数据失败", e);
184   - throw new RuntimeException(e);
185   - }
186   - }
187   -
188   -
189   - /**
190   - * 提交导入数据到数据库
191   - */
192   - @PostMapping("/submit")
193   - @Operation(summary = "批次保存")
194   - public CommonResult submitImport(@RequestParam("batchId") String batchId,
195   - @RequestParam("templateName") String templateName,
196   - @RequestParam("operator") String operator) {
197   - // 参数校验
198   - if (batchId == null || batchId.trim().isEmpty()) {
199   - return CommonResult.error(BAD_REQUEST, "批次ID不能为空");
200   - }
201   - if (templateName == null || templateName.trim().isEmpty()) {
202   - return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
203   -
204   - }
205   - if (TemplateEnum.getByCode(templateName) == null) {
206   - return CommonResult.error(BAD_REQUEST, "未知的模板类型");
207   -
208   - }
209   - if (operator == null || operator.trim().isEmpty()) {
210   - return CommonResult.error(BAD_REQUEST, "操作人不能为空");
211   - }
212   - int count = costFeeService.submitImport(batchId, templateName, operator);
213   - return CommonResult.success("导入成功,共" + count + "条数据");
214   - }
215   -
216   - /**
217   - * 写入模板文件到响应流
218   - */
219   - private void writeTemplateToResponse(HttpServletResponse response, String filename, String templatePath) throws IOException {
220   - // 设置响应头
221   - response.setContentType(EXCEL_MIME_TYPE);
222   - response.setCharacterEncoding("UTF-8");
223   - String encodedFileName = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
224   - response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
225   - response.setHeader("Cache-Control", "no-store"); // 禁止缓存
226   -
227   - // 读取模板文件并写入响应
228   - try (InputStream in = new ClassPathResource("template/" + templatePath).getInputStream();
229   - OutputStream out = response.getOutputStream()) {
230   -
231   - byte[] buffer = new byte[4096];
232   - int bytesRead;
233   - while ((bytesRead = in.read(buffer)) != -1) {
234   - out.write(buffer, 0, bytesRead);
235   - }
236   - out.flush();
237   - }
238   - }
239   -
240   -
241   - /**
242   - * 校验Excel文件有效性(后缀+MIME类型)
243   - */
244   - private boolean isValidExcelFile(MultipartFile file) {
245   - if (file == null || file.isEmpty()) {
246   - return false;
247   - }
248   - String fileName = file.getOriginalFilename();
249   - if (fileName == null) {
250   - return false;
251   - }
252   -
253   - // 校验文件后缀
254   - boolean validSuffix = ALLOWED_EXCEL_SUFFIX.stream()
255   - .anyMatch(suffix -> fileName.toLowerCase().endsWith(suffix));
256   - if (!validSuffix) {
257   - return false;
258   - }
259   -
260   - // 4. 校验MIME类型(兼容可能带参数的MIME类型,如charset)
261   - String contentType = file.getContentType();
262   - if (contentType == null) {
263   - return false;
264   - }
265   - // 处理MIME类型可能带参数的情况(如"application/xxx;charset=UTF-8")
266   - String baseContentType = contentType.split(";")[0].trim();
267   - // 3. 匹配预设的Excel MIME类型(.xlsx/.xlsm或.xls)
268   - return EXCEL_MIME_TYPE.equals(baseContentType)
269   - || baseContentType.startsWith(EXCEL_MIME_TYPE_XLS);
270   - }
271   -
272   -
273   - /**
274   - * 解析Excel文件并返回导入结果
275   - */
276   - @SuppressWarnings("unchecked")
277   - private <T> ImportResult<T> parseExcelFile(MultipartFile file, TemplateEnum template){
278   - // 获取模板对应的导入类
279   - Class<T> importClass = (Class<T>) template.getImportClass();
280   - List<T> dataList = null;
281   - try {
282   - // 解析Excel(从指定行开始读取)
283   - dataList = ExcelUtils.read(file, importClass, template.getStartIndex());
284   - if (dataList == null) {
285   - dataList = Collections.emptyList();
286   - }
287   - // 关键:移除最后一行(以“说明”开头的行)
288   - removeLastLineIfStartWithExplain(dataList, importClass);
289   - // 基础数据校验(非空、格式等)
290   - costFeeService.validateImportData(dataList, template);
291   -
292   - } catch (IOException e) {
293   - log.error(e.getMessage());
294   - }
295   - return new ImportResult<>(dataList, template.isNeedPreview());
296   - }
297   - /**
298   - * 移除列表最后一行(若该行第一列内容以“说明”开头)
299   - * @param dataList Excel解析后的实体类列表
300   - * @param clazz 实体类字节码(用于反射获取第一列字段值)
301   - * @param <T> 泛型:Excel对应的实体类
302   - */
303   - private <T> void removeLastLineIfStartWithExplain(List<T> dataList, Class<T> clazz) {
304   - // 1. 跳过空列表/元素数<1的情况,避免索引越界
305   - if (CollectionUtils.isEmpty(dataList)) {
306   - return;
307   - }
308   -
309   - // 2. 获取最后一个元素(正确索引:size()-1)
310   - T lastItem = dataList.get(dataList.size() - 1);
311   - if (Objects.isNull(lastItem)) {
312   - return;
313   - }
314   -
315   - // 3. 反射获取「第一列字段的值」(核心:Excel列对应实体类的字段,假设第一列字段名是 firstColumn,需根据实际调整)
316   - String firstColumnValue = getFirstColumnValue(lastItem, clazz);
317   - if (Objects.isNull(firstColumnValue)) {
318   - return;
319   - }
320   -
321   - // 4. 匹配“说明”(trim() 避免空格干扰,如“ 说明:xxx”)
322   - if (firstColumnValue.trim().startsWith("说明")) {
323   - // 移除最后一个元素(正确索引:size()-1)
324   - dataList.remove(dataList.size() - 1);
325   - }
326   - }
327   -
328   - /**
329   - * 反射获取实体类的「第一列字段值」(需根据你的Excel列配置调整字段名)
330   - * @param item 实体类对象(Excel一行数据)
331   - * @param clazz 实体类字节码
332   - * @return 第一列的字符串值(null 表示无值)
333   - */
334   - private <T> String getFirstColumnValue(T item, Class<T> clazz) {
335   - try {
336   - String firstColumnFieldName = "";
337   - if (WaterFeeImport.class.isAssignableFrom(clazz)) {
338   - firstColumnFieldName = "feeMonth";
339   - } else {
340   - return null;
341   - }
342   -
343   - // 反射获取 getter 方法()
344   - String getterMethodName = "get" + firstColumnFieldName.substring(0, 1).toUpperCase() + firstColumnFieldName.substring(1);
345   - Method getterMethod = clazz.getDeclaredMethod(getterMethodName);
346   -
347   - // 执行 getter 方法获取字段值
348   - Object value = getterMethod.invoke(item);
349   - return value == null ? null : value.toString();
350   - } catch (Exception e) {
351   - // 反射失败(字段名错误、无getter方法),打印日志不抛异常,避免影响整体流程
352   - log.warn("获取Excel第一列字段值失败,实体类:{}", clazz.getName(), e);
353   - return null;
354   - }
355   - }
356   -
357   - /**
358   - * 导入结果封装
359   - */
360   - @Data
361   - @AllArgsConstructor
362   - private static class ImportResult<T> {
363   - private List<T> dataList;
364   - private boolean needPreview;
365   - }
366   -
367   - /**
368   - * 分页结果封装
369   - */
370   - @Data
371   - @AllArgsConstructor
372   - public static class PageResult<T> {
373   - private int page;
374   - private int size;
375   - private int total;
376   - private List<T> data;
377   - }
378   -}
  1 +//package com.zteits.urbanops.module.garden.controller.admin.costfee;
  2 +//
  3 +//import com.zteits.urbanops.framework.common.pojo.CommonResult;
  4 +//import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
  5 +//import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.WaterFeeImport;
  6 +//import com.zteits.urbanops.module.garden.enums.TemplateEnum;
  7 +//import com.zteits.urbanops.module.garden.service.costfee.CostFeeService;
  8 +//import io.swagger.v3.oas.annotations.Operation;
  9 +//import io.swagger.v3.oas.annotations.tags.Tag;
  10 +//import jakarta.servlet.http.HttpServletResponse;
  11 +//import lombok.AllArgsConstructor;
  12 +//import lombok.Data;
  13 +//import lombok.extern.slf4j.Slf4j;
  14 +//import org.springframework.beans.factory.annotation.Autowired;
  15 +//import org.springframework.beans.factory.annotation.Value;
  16 +//import org.springframework.core.io.ClassPathResource;
  17 +//import org.springframework.util.CollectionUtils;
  18 +//import org.springframework.web.bind.annotation.*;
  19 +//import org.springframework.web.multipart.MultipartFile;
  20 +//
  21 +//import java.io.IOException;
  22 +//import java.io.InputStream;
  23 +//import java.io.OutputStream;
  24 +//import java.lang.reflect.Method;
  25 +//import java.net.URLEncoder;
  26 +//import java.nio.charset.StandardCharsets;
  27 +//import java.util.*;
  28 +//
  29 +//import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  30 +//import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
  31 +//
  32 +///**
  33 +// * 人机材公共处理控制器
  34 +// * 处理Excel导入预览、提交等功能
  35 +// */
  36 +//@Tag(name = "管理后台 - 人机材批量导入")
  37 +//@RestController
  38 +//@RequestMapping("/costfee")
  39 +//@Slf4j
  40 +//public class CostFeeController{
  41 +//
  42 +// @Autowired
  43 +// private CostFeeService costFeeService;
  44 +//
  45 +// // 静态资源服务器基础URL(从配置文件读取)
  46 +// @Value("${static.resource.server.url}")
  47 +// private String staticResourceServerUrl;
  48 +//
  49 +// @Value("${static.resource.server.enable}")
  50 +// private Boolean staticResourceServerenable;
  51 +//
  52 +// // Excel文件校验相关
  53 +// private static final List<String> ALLOWED_EXCEL_SUFFIX = Arrays.asList(".xlsx", ".xlsm");
  54 +// private static final String EXCEL_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
  55 +// private static final String EXCEL_MIME_TYPE_XLS = "application/vnd.ms-excel";
  56 +//
  57 +// /**
  58 +// * 导入模板下载
  59 +// */
  60 +// @GetMapping("/get-import-template")
  61 +// @Operation(summary = "获得导入用户模板")
  62 +// public void importTemplate(HttpServletResponse response,
  63 +// @RequestParam("templateName") String templateName) throws IOException {
  64 +// // 参数校验
  65 +// if (templateName == null || templateName.trim().isEmpty()) {
  66 +// response.sendError(HttpServletResponse.SC_BAD_REQUEST, "模板名称不能为空");
  67 +// return;
  68 +// }
  69 +//
  70 +// // 模板类型校验
  71 +// TemplateEnum template = TemplateEnum.getByCode(templateName);
  72 +// if (template == null) {
  73 +// response.sendError(HttpServletResponse.SC_BAD_REQUEST, "未知的模板类型: " + templateName);
  74 +// return;
  75 +// }
  76 +//
  77 +// if (staticResourceServerenable) {
  78 +// // 构建完整的静态服务器URL(确保路径正确)
  79 +// String serverUrl = staticResourceServerUrl;
  80 +// if (serverUrl != null && !serverUrl.endsWith("/")) {
  81 +// serverUrl += "/"; // 确保URL以/结尾,避免拼接错误
  82 +// }
  83 +//
  84 +// String encodedFileName = URLEncoder.encode(template.getName().split("\\.")[0], StandardCharsets.UTF_8.name());
  85 +//
  86 +// String templateFileName = encodedFileName + "." + template.getName().split("\\.")[1];
  87 +//
  88 +// String templateUrl = serverUrl + templateFileName; // 包含templates目录
  89 +//
  90 +// // 方式1:重定向到静态资源服务器
  91 +// response.sendRedirect(templateUrl);
  92 +// } else {
  93 +// String templateFileName = templateName + "." + template.getName().split("\\.")[1];
  94 +//
  95 +// // 写入模板文件到响应
  96 +// writeTemplateToResponse(response, template.getName(), templateFileName);
  97 +// }
  98 +// }
  99 +//
  100 +//
  101 +// /**
  102 +// * Excel文件上传预览
  103 +// */
  104 +// @PostMapping("/preview")
  105 +// @Operation(summary = "文件上传")
  106 +// public CommonResult previewExcel(@RequestParam("file") MultipartFile file,
  107 +// @RequestParam("templateName") String templateName) {
  108 +// // 基础参数校验
  109 +// if (templateName == null || templateName.trim().isEmpty()) {
  110 +// return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
  111 +//
  112 +// }
  113 +// // 模板类型校验
  114 +// TemplateEnum template = TemplateEnum.getByCode(templateName);
  115 +// if (template == null) {
  116 +// return CommonResult.error(BAD_REQUEST, "未知的模板类型");
  117 +// }
  118 +// String suffix = template.getName().split("\\.")[1];
  119 +// // 文件有效性校验
  120 +// if (!isValidExcelFile(file)) {
  121 +// return CommonResult.error(BAD_REQUEST, "请上传有效的Excel文件");
  122 +// }
  123 +// //部门信息缓存,校验正确性使用
  124 +//
  125 +//
  126 +// // 解析Excel并获取数据
  127 +// ImportResult<?> result = parseExcelFile(file, template);
  128 +//
  129 +// // 需预览的数据存入Redis并返回批次ID
  130 +// if (result.isNeedPreview()) {
  131 +// if (CollectionUtils.isEmpty(result.getDataList())) {
  132 +// return CommonResult.error(BAD_REQUEST, "导入的Excel文件不能为空");
  133 +//
  134 +// }
  135 +// String batchId = costFeeService.saveDataToRedis(result.getDataList(), templateName);
  136 +// Map<String, Object> resultMap = new HashMap<>(2);
  137 +// resultMap.put("batchId", batchId);
  138 +// resultMap.put("totalCount", result.getDataList().size());
  139 +// resultMap.put("templateName",templateName);
  140 +// return success(resultMap);
  141 +// }
  142 +// // 无需预览的直接返回数据
  143 +// return success(result.getDataList());
  144 +// }
  145 +//
  146 +//
  147 +// /**
  148 +// * 分页获取预览数据
  149 +// */
  150 +// @GetMapping("/preview-data")
  151 +// @Operation(summary = "批量上传分页预览")
  152 +// public CommonResult getPreviewData(@RequestParam("batchId") String batchId,
  153 +// @RequestParam("templateName") String templateName,
  154 +// @RequestParam(value = "page", defaultValue = "1") int page,
  155 +// @RequestParam(value = "size", defaultValue = "100000") int size) {
  156 +//
  157 +//
  158 +// // 参数校验
  159 +// if (batchId == null || batchId.trim().isEmpty()) {
  160 +// return CommonResult.error(BAD_REQUEST, "批次ID不能为空");
  161 +// }
  162 +// if (templateName == null || templateName.trim().isEmpty()) {
  163 +// return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
  164 +//
  165 +// }
  166 +// if (TemplateEnum.getByCode(templateName) == null) {
  167 +// return CommonResult.error(BAD_REQUEST, "未知的模板类型");
  168 +//
  169 +// }
  170 +// try {
  171 +// //if (page < 1 || size < 1 || size > 100) { // 限制每页最大100条,避免过大
  172 +// // return AjaxResult.error("分页参数无效(page≥1,1≤size≤100)");
  173 +// //}
  174 +//
  175 +// // 模板类型校验
  176 +// TemplateEnum template = TemplateEnum.getByCode(templateName);
  177 +// Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch
  178 +//
  179 +// // 从Redis获取分页数据
  180 +// PageResult<?> pageResult = costFeeService.getPagedDataFromRedis(batchId, templateName, page, size, importClass);
  181 +// return CommonResult.success(pageResult);
  182 +// } catch (Exception e) {
  183 +// log.error("获取预览数据失败", e);
  184 +// throw new RuntimeException(e);
  185 +// }
  186 +// }
  187 +//
  188 +//
  189 +// /**
  190 +// * 提交导入数据到数据库
  191 +// */
  192 +// @PostMapping("/submit")
  193 +// @Operation(summary = "批次保存")
  194 +// public CommonResult submitImport(@RequestParam("batchId") String batchId,
  195 +// @RequestParam("templateName") String templateName,
  196 +// @RequestParam("operator") String operator) {
  197 +// // 参数校验
  198 +// if (batchId == null || batchId.trim().isEmpty()) {
  199 +// return CommonResult.error(BAD_REQUEST, "批次ID不能为空");
  200 +// }
  201 +// if (templateName == null || templateName.trim().isEmpty()) {
  202 +// return CommonResult.error(BAD_REQUEST, "模板名称不能为空");
  203 +//
  204 +// }
  205 +// if (TemplateEnum.getByCode(templateName) == null) {
  206 +// return CommonResult.error(BAD_REQUEST, "未知的模板类型");
  207 +//
  208 +// }
  209 +// if (operator == null || operator.trim().isEmpty()) {
  210 +// return CommonResult.error(BAD_REQUEST, "操作人不能为空");
  211 +// }
  212 +// int count = costFeeService.submitImport(batchId, templateName, operator);
  213 +// return CommonResult.success("导入成功,共" + count + "条数据");
  214 +// }
  215 +//
  216 +// /**
  217 +// * 写入模板文件到响应流
  218 +// */
  219 +// private void writeTemplateToResponse(HttpServletResponse response, String filename, String templatePath) throws IOException {
  220 +// // 设置响应头
  221 +// response.setContentType(EXCEL_MIME_TYPE);
  222 +// response.setCharacterEncoding("UTF-8");
  223 +// String encodedFileName = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
  224 +// response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
  225 +// response.setHeader("Cache-Control", "no-store"); // 禁止缓存
  226 +//
  227 +// // 读取模板文件并写入响应
  228 +// try (InputStream in = new ClassPathResource("template/" + templatePath).getInputStream();
  229 +// OutputStream out = response.getOutputStream()) {
  230 +//
  231 +// byte[] buffer = new byte[4096];
  232 +// int bytesRead;
  233 +// while ((bytesRead = in.read(buffer)) != -1) {
  234 +// out.write(buffer, 0, bytesRead);
  235 +// }
  236 +// out.flush();
  237 +// }
  238 +// }
  239 +//
  240 +//
  241 +// /**
  242 +// * 校验Excel文件有效性(后缀+MIME类型)
  243 +// */
  244 +// private boolean isValidExcelFile(MultipartFile file) {
  245 +// if (file == null || file.isEmpty()) {
  246 +// return false;
  247 +// }
  248 +// String fileName = file.getOriginalFilename();
  249 +// if (fileName == null) {
  250 +// return false;
  251 +// }
  252 +//
  253 +// // 校验文件后缀
  254 +// boolean validSuffix = ALLOWED_EXCEL_SUFFIX.stream()
  255 +// .anyMatch(suffix -> fileName.toLowerCase().endsWith(suffix));
  256 +// if (!validSuffix) {
  257 +// return false;
  258 +// }
  259 +//
  260 +// // 4. 校验MIME类型(兼容可能带参数的MIME类型,如charset)
  261 +// String contentType = file.getContentType();
  262 +// if (contentType == null) {
  263 +// return false;
  264 +// }
  265 +// // 处理MIME类型可能带参数的情况(如"application/xxx;charset=UTF-8")
  266 +// String baseContentType = contentType.split(";")[0].trim();
  267 +// // 3. 匹配预设的Excel MIME类型(.xlsx/.xlsm或.xls)
  268 +// return EXCEL_MIME_TYPE.equals(baseContentType)
  269 +// || baseContentType.startsWith(EXCEL_MIME_TYPE_XLS);
  270 +// }
  271 +//
  272 +//
  273 +// /**
  274 +// * 解析Excel文件并返回导入结果
  275 +// */
  276 +// @SuppressWarnings("unchecked")
  277 +// private <T> ImportResult<T> parseExcelFile(MultipartFile file, TemplateEnum template){
  278 +// // 获取模板对应的导入类
  279 +// Class<T> importClass = (Class<T>) template.getImportClass();
  280 +// List<T> dataList = null;
  281 +// try {
  282 +// // 解析Excel(从指定行开始读取)
  283 +// dataList = ExcelUtils.read(file, importClass, template.getStartIndex());
  284 +// if (dataList == null) {
  285 +// dataList = Collections.emptyList();
  286 +// }
  287 +// // 关键:移除最后一行(以“说明”开头的行)
  288 +// removeLastLineIfStartWithExplain(dataList, importClass);
  289 +// // 基础数据校验(非空、格式等)
  290 +// costFeeService.validateImportData(dataList, template);
  291 +//
  292 +// } catch (IOException e) {
  293 +// log.error(e.getMessage());
  294 +// }
  295 +// return new ImportResult<>(dataList, template.isNeedPreview());
  296 +// }
  297 +// /**
  298 +// * 移除列表最后一行(若该行第一列内容以“说明”开头)
  299 +// * @param dataList Excel解析后的实体类列表
  300 +// * @param clazz 实体类字节码(用于反射获取第一列字段值)
  301 +// * @param <T> 泛型:Excel对应的实体类
  302 +// */
  303 +// private <T> void removeLastLineIfStartWithExplain(List<T> dataList, Class<T> clazz) {
  304 +// // 1. 跳过空列表/元素数<1的情况,避免索引越界
  305 +// if (CollectionUtils.isEmpty(dataList)) {
  306 +// return;
  307 +// }
  308 +//
  309 +// // 2. 获取最后一个元素(正确索引:size()-1)
  310 +// T lastItem = dataList.get(dataList.size() - 1);
  311 +// if (Objects.isNull(lastItem)) {
  312 +// return;
  313 +// }
  314 +//
  315 +// // 3. 反射获取「第一列字段的值」(核心:Excel列对应实体类的字段,假设第一列字段名是 firstColumn,需根据实际调整)
  316 +// String firstColumnValue = getFirstColumnValue(lastItem, clazz);
  317 +// if (Objects.isNull(firstColumnValue)) {
  318 +// return;
  319 +// }
  320 +//
  321 +// // 4. 匹配“说明”(trim() 避免空格干扰,如“ 说明:xxx”)
  322 +// if (firstColumnValue.trim().startsWith("说明")) {
  323 +// // 移除最后一个元素(正确索引:size()-1)
  324 +// dataList.remove(dataList.size() - 1);
  325 +// }
  326 +// }
  327 +//
  328 +// /**
  329 +// * 反射获取实体类的「第一列字段值」(需根据你的Excel列配置调整字段名)
  330 +// * @param item 实体类对象(Excel一行数据)
  331 +// * @param clazz 实体类字节码
  332 +// * @return 第一列的字符串值(null 表示无值)
  333 +// */
  334 +// private <T> String getFirstColumnValue(T item, Class<T> clazz) {
  335 +// try {
  336 +// String firstColumnFieldName = "";
  337 +// if (WaterFeeImport.class.isAssignableFrom(clazz)) {
  338 +// firstColumnFieldName = "feeMonth";
  339 +// } else {
  340 +// return null;
  341 +// }
  342 +//
  343 +// // 反射获取 getter 方法()
  344 +// String getterMethodName = "get" + firstColumnFieldName.substring(0, 1).toUpperCase() + firstColumnFieldName.substring(1);
  345 +// Method getterMethod = clazz.getDeclaredMethod(getterMethodName);
  346 +//
  347 +// // 执行 getter 方法获取字段值
  348 +// Object value = getterMethod.invoke(item);
  349 +// return value == null ? null : value.toString();
  350 +// } catch (Exception e) {
  351 +// // 反射失败(字段名错误、无getter方法),打印日志不抛异常,避免影响整体流程
  352 +// log.warn("获取Excel第一列字段值失败,实体类:{}", clazz.getName(), e);
  353 +// return null;
  354 +// }
  355 +// }
  356 +//
  357 +// /**
  358 +// * 导入结果封装
  359 +// */
  360 +// @Data
  361 +// @AllArgsConstructor
  362 +// private static class ImportResult<T> {
  363 +// private List<T> dataList;
  364 +// private boolean needPreview;
  365 +// }
  366 +//
  367 +// /**
  368 +// * 分页结果封装
  369 +// */
  370 +// @Data
  371 +// @AllArgsConstructor
  372 +// public static class PageResult<T> {
  373 +// private int page;
  374 +// private int size;
  375 +// private int total;
  376 +// private List<T> data;
  377 +// }
  378 +//}
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeService.java
1   -package com.zteits.urbanops.module.garden.service.costfee;
2   -
3   -import com.fasterxml.jackson.core.JsonProcessingException;
4   -import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController;
5   -import com.zteits.urbanops.module.garden.enums.TemplateEnum;
6   -
7   -import java.io.IOException;
8   -import java.util.List;
9   -import java.util.Map;
10   -
11   -/**
12   - * @Classname ICostFeeService
13   - * @Description 模板导入公共接口
14   - * @Date 2025/7/20 15:10
15   - * @Created by wangqian
16   - */
17   -public interface CostFeeService {
18   -
19   - /**
20   - * @Author wangqian
21   - * @Description 导入数据保存redis
22   - * @Date 2025/7/20 15:36
23   - * @Param dataList
24   - * @param templateName
25   - * @Return java.lang.String
26   - */
27   - public String saveDataToRedis(List<?> dataList, String templateName);
28   -
29   - /**
30   - * @Author wangqian
31   - * @Description 导入数据保存到数据库
32   - * @Date 2025/7/20 15:36
33   - * @Param batchId
34   - * @param templateName
35   - * @param operator
36   - * @Return int
37   - */
38   - public int submitImport(String batchId, String templateName, String operator);
39   -
40   - /**
41   - * @Author wangqian
42   - * @Description 预览分页查询
43   - * @Date 2025/7/20 15:36
44   - * @Param batchId
45   - * @param templateName
46   - * @param page
47   - * @param size
48   - * @param clazz
49   - * @Return com.servicemanager.project.costfee.controller.CostFeeController.PageResult<T>
50   - */
51   - public <T> CostFeeController.PageResult<T> getPagedDataFromRedis(String batchId, String templateName,
52   - int page, int size, Class<T> clazz) throws IOException;
53   -
54   - /**
55   - * @Author wangqian
56   - * @Description 导入内容校验
57   - * @Date 2025/11/24 8:40
58   - * @Param dataList
59   - * @param template
60   - * @Return void
61   - */
62   - public void validateImportData(List dataList, TemplateEnum template);
63   -
64   -}
  1 +//package com.zteits.urbanops.module.garden.service.costfee;
  2 +//
  3 +//import com.fasterxml.jackson.core.JsonProcessingException;
  4 +//import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController;
  5 +//import com.zteits.urbanops.module.garden.enums.TemplateEnum;
  6 +//
  7 +//import java.io.IOException;
  8 +//import java.util.List;
  9 +//import java.util.Map;
  10 +//
  11 +///**
  12 +// * @Classname ICostFeeService
  13 +// * @Description 模板导入公共接口
  14 +// * @Date 2025/7/20 15:10
  15 +// * @Created by wangqian
  16 +// */
  17 +//public interface CostFeeService {
  18 +//
  19 +// /**
  20 +// * @Author wangqian
  21 +// * @Description 导入数据保存redis
  22 +// * @Date 2025/7/20 15:36
  23 +// * @Param dataList
  24 +// * @param templateName
  25 +// * @Return java.lang.String
  26 +// */
  27 +// public String saveDataToRedis(List<?> dataList, String templateName);
  28 +//
  29 +// /**
  30 +// * @Author wangqian
  31 +// * @Description 导入数据保存到数据库
  32 +// * @Date 2025/7/20 15:36
  33 +// * @Param batchId
  34 +// * @param templateName
  35 +// * @param operator
  36 +// * @Return int
  37 +// */
  38 +// public int submitImport(String batchId, String templateName, String operator);
  39 +//
  40 +// /**
  41 +// * @Author wangqian
  42 +// * @Description 预览分页查询
  43 +// * @Date 2025/7/20 15:36
  44 +// * @Param batchId
  45 +// * @param templateName
  46 +// * @param page
  47 +// * @param size
  48 +// * @param clazz
  49 +// * @Return com.servicemanager.project.costfee.controller.CostFeeController.PageResult<T>
  50 +// */
  51 +// public <T> CostFeeController.PageResult<T> getPagedDataFromRedis(String batchId, String templateName,
  52 +// int page, int size, Class<T> clazz) throws IOException;
  53 +//
  54 +// /**
  55 +// * @Author wangqian
  56 +// * @Description 导入内容校验
  57 +// * @Date 2025/11/24 8:40
  58 +// * @Param dataList
  59 +// * @param template
  60 +// * @Return void
  61 +// */
  62 +// public void validateImportData(List dataList, TemplateEnum template);
  63 +//
  64 +//}
... ...