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 +//}