Commit 174fba3353bc21a028a4dfed65b091f84bbb3347

Authored by wangqian
1 parent 9ce30ce5

模板下载动态赋值单位下拉选

urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java
... ... @@ -5,16 +5,25 @@ import com.zteits.urbanops.framework.excel.core.util.ExcelUtils;
5 5 import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.WaterFeeImport;
6 6 import com.zteits.urbanops.module.garden.enums.TemplateEnum;
7 7 import com.zteits.urbanops.module.garden.service.costfee.CostFeeService;
  8 +import com.zteits.urbanops.module.system.api.dept.DeptApi;
  9 +import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO;
8 10 import io.swagger.v3.oas.annotations.Operation;
9 11 import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.security.PermitAll;
10 13 import jakarta.servlet.http.HttpServletResponse;
11 14 import lombok.AllArgsConstructor;
12 15 import lombok.Data;
13 16 import lombok.extern.slf4j.Slf4j;
  17 +import org.apache.poi.ss.usermodel.*;
  18 +import org.apache.poi.ss.util.CellRangeAddress;
  19 +import org.apache.poi.ss.util.CellRangeAddressList;
  20 +import org.apache.poi.xssf.usermodel.*;
14 21 import org.springframework.beans.factory.annotation.Autowired;
15 22 import org.springframework.beans.factory.annotation.Value;
16   -import org.springframework.core.io.ClassPathResource;
  23 +import org.springframework.core.io.Resource;
  24 +import org.springframework.core.io.ResourceLoader;
17 25 import org.springframework.util.CollectionUtils;
  26 +import org.springframework.util.StringUtils;
18 27 import org.springframework.web.bind.annotation.*;
19 28 import org.springframework.web.multipart.MultipartFile;
20 29  
... ... @@ -25,6 +34,7 @@ import java.lang.reflect.Method;
25 34 import java.net.URLEncoder;
26 35 import java.nio.charset.StandardCharsets;
27 36 import java.util.*;
  37 +import java.util.stream.Collectors;
28 38  
29 39 import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
30 40 import static com.zteits.urbanops.framework.common.pojo.CommonResult.success;
... ... @@ -49,6 +59,12 @@ public class CostFeeController{
49 59 @Value("${static.resource.server.enable}")
50 60 private Boolean staticResourceServerenable;
51 61  
  62 + @Autowired
  63 + private ResourceLoader resourceLoader;
  64 +
  65 + @jakarta.annotation.Resource
  66 + private DeptApi deptApi;
  67 +
52 68 // Excel文件校验相关
53 69 private static final List<String> ALLOWED_EXCEL_SUFFIX = Arrays.asList(".xlsx", ".xlsm");
54 70 private static final String EXCEL_MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
... ... @@ -59,6 +75,7 @@ public class CostFeeController{
59 75 */
60 76 @GetMapping("/get-import-template")
61 77 @Operation(summary = "获得导入用户模板")
  78 + @PermitAll
62 79 public void importTemplate(HttpServletResponse response,
63 80 @RequestParam("templateName") String templateName) throws IOException {
64 81 // 参数校验
... ... @@ -74,29 +91,143 @@ public class CostFeeController{
74 91 return;
75 92 }
76 93  
77   - if (staticResourceServerenable) {
78   - // 构建完整的静态服务器URL(确保路径正确)
79   - String serverUrl = staticResourceServerUrl;
80   - if (serverUrl != null && !serverUrl.endsWith("/")) {
81   - serverUrl += "/"; // 确保URL以/结尾,避免拼接错误
82   - }
  94 + writeAndModifyTemplate(response, template.getName(), template.getStartIndex());
  95 + }
  96 +
  97 + private void writeAndModifyTemplate(HttpServletResponse response,
  98 + String downloadFileName,
  99 + int startIndexRow) throws IOException {
  100 +
  101 + // 拼接模板路径
  102 + String serverUrl = staticResourceServerUrl;
  103 + if (serverUrl != null && !serverUrl.endsWith("/")) {
  104 + serverUrl += "/";
  105 + }
  106 + String fullPath = serverUrl + downloadFileName;
  107 + fullPath = fullPath.replace("\\", "/");
  108 +
  109 + Resource resource = resourceLoader.getResource(fullPath);
  110 +
  111 + // 检查文件是否存在
  112 + if (!resource.exists()) {
  113 + response.sendError(HttpServletResponse.SC_NOT_FOUND, "模板文件不存在");
  114 + return;
  115 + }
  116 +
  117 + // 设置下载响应头
  118 + response.reset();
  119 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  120 + response.setCharacterEncoding("UTF-8");
  121 + String encodedName = URLEncoder.encode(downloadFileName, "UTF-8");
  122 + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encodedName);
  123 +
  124 + try (InputStream in = resource.getInputStream();
  125 + XSSFWorkbook workbook = new XSSFWorkbook(in);
  126 + OutputStream out = response.getOutputStream()) {
83 127  
84   - String encodedFileName = URLEncoder.encode(template.getName().split("\\.")[0], StandardCharsets.UTF_8.name());
  128 + XSSFSheet sheet = workbook.getSheetAt(0);
85 129  
86   - String templateFileName = encodedFileName + "." + template.getName().split("\\.")[1];
  130 + // ==============================================
  131 + // 获取最新单位列表
  132 + // ==============================================
  133 + List<DeptRespDTO> companyList = deptApi.getSubDeptList();
  134 + List<String> newUnits = companyList.stream()
  135 + .map(DeptRespDTO::getName)
  136 + .filter(Objects::nonNull)
  137 + .toList();
87 138  
88   - String templateUrl = serverUrl + templateFileName; // 包含templates目录
  139 + log.info("加载到单位列表:{},共{}条", newUnits, newUnits.size());
89 140  
90   - // 方式1:重定向到静态资源服务器
91   - response.sendRedirect(templateUrl);
92   - } else {
93   - String templateFileName = templateName + "." + template.getName().split("\\.")[1];
  141 + // ==============================================
  142 + // 生成新下拉 + 自动赋值
  143 + // ==============================================
  144 + createDropdownAndSetValue(sheet, newUnits, startIndexRow);
94 145  
95   - // 写入模板文件到响应
96   - writeTemplateToResponse(response, template.getName(), templateFileName);
  146 + // 写入并强制刷新
  147 + workbook.write(out);
  148 + out.flush();
  149 +
  150 + } catch (Exception e) {
  151 + log.error("模板处理失败", e);
  152 + throw new IOException("文件处理失败", e);
97 153 }
98 154 }
99 155  
  156 + /**
  157 + * 创建下拉框 + 给单元格赋值
  158 + */
  159 + private void createDropdownAndSetValue(XSSFSheet sheet, List<String> options, int startRow) {
  160 + if (options.isEmpty()) {
  161 + log.error("下拉选项为空,不创建");
  162 + return;
  163 + }
  164 +
  165 + // 获取表头行
  166 + Row headerRow = sheet.getRow(startRow - 1);
  167 + if (headerRow == null) {
  168 + log.error("表头行不存在:{}", startRow - 1);
  169 + return;
  170 + }
  171 +
  172 + // 查找“所属单位”列
  173 + int targetCol = -1;
  174 + for (Cell cell : headerRow) {
  175 + if (cell != null && cell.getCellType() == CellType.STRING) {
  176 + String val = cell.getStringCellValue().trim();
  177 + if (val.contains("所属单位") || val.contains("所属公司") ) {
  178 + targetCol = cell.getColumnIndex();
  179 + break;
  180 + }
  181 + }
  182 + }
  183 +
  184 + if (targetCol == -1) {
  185 + log.error("未找到【所属单位】列表头");
  186 + return;
  187 + }
  188 +
  189 + // 取消列隐藏
  190 + sheet.setColumnHidden(targetCol, false);
  191 +
  192 + // ==============================================
  193 + // 遍历行:清空原有内容 + 设置默认值
  194 + // ==============================================
  195 + int endRow = 100;
  196 + for (int r = startRow; r <= endRow; r++) {
  197 + Row row = sheet.getRow(r);
  198 + if (row == null) {
  199 + row = sheet.createRow(r);
  200 + }
  201 +
  202 + Cell cell = row.getCell(targetCol);
  203 + if (cell == null) {
  204 + cell = row.createCell(targetCol);
  205 + }
  206 + String unit = cell.getStringCellValue();
  207 + // 默认赋值【第一个单位】,你可以根据业务改成其他值
  208 + if (!StringUtils.isEmpty(unit)) {
  209 + cell.setCellValue(options.get(2));
  210 + }
  211 + }
  212 +
  213 + // ==============================================
  214 + // 创建下拉框(作用范围:startRow ~ 1000行)
  215 + // ==============================================
  216 + CellRangeAddressList regions = new CellRangeAddressList(startRow, endRow, targetCol, targetCol);
  217 + XSSFDataValidationHelper helper = new XSSFDataValidationHelper(sheet);
  218 + XSSFDataValidationConstraint constraint = (XSSFDataValidationConstraint) helper.createExplicitListConstraint(
  219 + options.toArray(new String[0])
  220 + );
  221 + XSSFDataValidation validation = (XSSFDataValidation) helper.createValidation(constraint, regions);
  222 +
  223 + // 下拉框基础配置
  224 + validation.setEmptyCellAllowed(true);
  225 + validation.setSuppressDropDownArrow(true);
  226 + sheet.addValidationData(validation);
  227 +
  228 + log.info("【成功】为列{}添加下拉并赋值,行{}~{},选项数:{}",
  229 + targetCol, startRow, endRow, options.size());
  230 + }
100 231  
101 232 /**
102 233 * Excel文件上传预览
... ... @@ -214,31 +345,6 @@ public class CostFeeController{
214 345 }
215 346  
216 347 /**
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 348 * 校验Excel文件有效性(后缀+MIME类型)
243 349 */
244 350 private boolean isValidExcelFile(MultipartFile file) {
... ...