diff --git a/mh-admin/src/main/java/com/mh/web/controller/project/ProjectInfoController.java b/mh-admin/src/main/java/com/mh/web/controller/project/ProjectInfoController.java new file mode 100644 index 0000000..c64d89b --- /dev/null +++ b/mh-admin/src/main/java/com/mh/web/controller/project/ProjectInfoController.java @@ -0,0 +1,85 @@ +package com.mh.web.controller.project; + +import com.mh.common.annotation.Log; +import com.mh.common.core.controller.BaseController; +import com.mh.common.core.domain.AjaxResult; +import com.mh.common.core.domain.entity.ProjectInfo; +import com.mh.common.core.page.TableDataInfo; +import com.mh.common.enums.BusinessType; +import com.mh.system.service.project.IProjectInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; + +/** + * @author EEMCS + * @version 1.0 + * @project EEMCS + * @description 项目信息管理 + * @date 2026-06-11 + */ +@RestController +@RequestMapping("/project/info") +public class ProjectInfoController extends BaseController { + + @Autowired + private IProjectInfoService projectInfoService; + + /** + * 获取项目信息列表 + */ + @PreAuthorize("@ss.hasPermi('project:info:list')") + @GetMapping("/list") + public TableDataInfo list(ProjectInfo projectInfo) { + startPage(); + List list = projectInfoService.selectProjectInfoList(projectInfo); + return getDataTable(list); + } + + /** + * 根据项目ID获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('project:info:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable String id) { + return success(projectInfoService.selectProjectInfoById(id)); + } + + /** + * 新增项目信息 + */ + @PreAuthorize("@ss.hasPermi('project:info:add')") + @Log(title = "项目信息管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody ProjectInfo projectInfo) { + projectInfo.setCreateBy(getUsername()); + projectInfo.setCreateTime(new Date()); + return toAjax(projectInfoService.insertProjectInfo(projectInfo)); + } + + /** + * 修改项目信息 + */ + @PreAuthorize("@ss.hasPermi('project:info:edit')") + @Log(title = "项目信息管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody ProjectInfo projectInfo) { + projectInfo.setUpdateBy(getUsername()); + projectInfo.setUpdateTime(new Date()); + return toAjax(projectInfoService.updateProjectInfo(projectInfo)); + } + + /** + * 删除项目信息 + */ + @PreAuthorize("@ss.hasPermi('project:info:remove')") + @Log(title = "项目信息管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable String[] ids) { + return toAjax(projectInfoService.deleteProjectInfoByIds(ids)); + } +} diff --git a/mh-admin/src/test/java/com/mh/web/controller/ai/AiFileControllerTest.java b/mh-admin/src/test/java/com/mh/web/controller/ai/AiFileControllerTest.java new file mode 100644 index 0000000..dbadec9 --- /dev/null +++ b/mh-admin/src/test/java/com/mh/web/controller/ai/AiFileControllerTest.java @@ -0,0 +1,40 @@ +package com.mh.web.controller.ai; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class AiFileControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void testGetFileDownload() throws Exception { + // 测试GET请求下载文件 + mockMvc.perform(get("/ai/files/测试文件.txt")) + .andExpect(status().isNotFound()); // 文件不存在返回404 + } + + @Test + public void testPostDeviceAnalyzeExport() throws Exception { + // 测试POST请求设备分析导出 + mockMvc.perform(post("/ai/files/设备分析报表.xlsx")) + .andExpect(status().isNotFound()); // 文件不存在返回404 + } + + @Test + public void testChineseFileNameHandling() throws Exception { + // 测试中文文件名处理 + mockMvc.perform(get("/ai/files/能耗报表_2026-02-27.docx")) + .andExpect(status().isNotFound()); // 文件不存在返回404,但不应出现映射冲突 + } +} \ No newline at end of file diff --git a/mh-admin/src/test/java/com/mh/web/controller/ai/FileNameEncodingTest.java b/mh-admin/src/test/java/com/mh/web/controller/ai/FileNameEncodingTest.java new file mode 100644 index 0000000..1ed8e64 --- /dev/null +++ b/mh-admin/src/test/java/com/mh/web/controller/ai/FileNameEncodingTest.java @@ -0,0 +1,52 @@ +package com.mh.web.controller.ai; + +import org.junit.jupiter.api.Test; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FileNameEncodingTest { + + @Test + public void testChineseFileNameEncoding() throws Exception { + String chineseFileName = "能耗报表_2026-02-27.docx"; + + // 测试RFC 5987格式编码 + String encodedFileName = URLEncoder.encode(chineseFileName, StandardCharsets.UTF_8.toString()); + String disposition = String.format("attachment; filename*=UTF-8''%s", encodedFileName); + + System.out.println("原始文件名: " + chineseFileName); + System.out.println("编码后文件名: " + encodedFileName); + System.out.println("完整Content-Disposition头: " + disposition); + + // 验证编码后的字符串不包含中文字符 + assertTrue(encodedFileName.matches("[A-Za-z0-9%_-]+")); + // 验证包含预期的编码字符 + assertTrue(encodedFileName.contains("%")); + } + + @Test + public void testDifferentChineseFileNames() throws Exception { + String[] testFiles = { + "系统报表.xlsx", + "数据分析报告.pdf", + "能耗统计_2026年2月.docx", + "设备运行状态.csv" + }; + + for (String fileName : testFiles) { + String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()); + String disposition = String.format("attachment; filename*=UTF-8''%s", encoded); + + System.out.println("文件名: " + fileName); + System.out.println("编码: " + encoded); + System.out.println("Disposition: " + disposition); + System.out.println("---"); + + // 验证编码正确 + assertTrue(encoded.matches("[A-Za-z0-9%_-]+")); + } + } +} \ No newline at end of file diff --git a/mh-common/src/main/java/com/mh/common/core/domain/entity/ProjectInfo.java b/mh-common/src/main/java/com/mh/common/core/domain/entity/ProjectInfo.java new file mode 100644 index 0000000..95e8f54 --- /dev/null +++ b/mh-common/src/main/java/com/mh/common/core/domain/entity/ProjectInfo.java @@ -0,0 +1,274 @@ +package com.mh.common.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.mh.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.Map; + +/** + * @author EEMCS + * @version 1.0 + * @project EEMCS + * @description 项目信息管理 + * @date 2026-06-11 + */ +@TableName("project_info") +public class ProjectInfo extends BaseEntity { + + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + /** + * 用户项目编码 + */ + private String projectCode; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 项目地址 + */ + private String projectAddress; + + /** + * 安装日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date installDate; + + /** + * 验收日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date acceptanceDate; + + /** + * 上线日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date onlineDate; + + /** + * 设备数量 + */ + private Integer deviceCount; + + /** + * 在线设备数量 + */ + private Integer onlineDeviceCount; + + /** + * 离线设备数量 + */ + private Integer offlineDeviceCount; + + /** + * 网关数量 + */ + private Integer gatewayCount; + + /** + * 在线网关数量 + */ + private Integer onlineGatewayCount; + + /** + * 离线网关数量 + */ + private Integer offlineGatewayCount; + + /** + * 项目负责人 + */ + private String projectManager; + + /** + * 电单价 + */ + private BigDecimal electricityPrice; + + @JsonIgnore + @TableField(exist = false) + private String searchValue; + + /** 请求参数 */ + @TableField(exist = false) + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getProjectCode() { + return projectCode; + } + + public void setProjectCode(String projectCode) { + this.projectCode = projectCode; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectAddress() { + return projectAddress; + } + + public void setProjectAddress(String projectAddress) { + this.projectAddress = projectAddress; + } + + public Date getInstallDate() { + return installDate; + } + + public void setInstallDate(Date installDate) { + this.installDate = installDate; + } + + public Date getAcceptanceDate() { + return acceptanceDate; + } + + public void setAcceptanceDate(Date acceptanceDate) { + this.acceptanceDate = acceptanceDate; + } + + public Date getOnlineDate() { + return onlineDate; + } + + public void setOnlineDate(Date onlineDate) { + this.onlineDate = onlineDate; + } + + public Integer getDeviceCount() { + return deviceCount; + } + + public void setDeviceCount(Integer deviceCount) { + this.deviceCount = deviceCount; + } + + public Integer getOnlineDeviceCount() { + return onlineDeviceCount; + } + + public void setOnlineDeviceCount(Integer onlineDeviceCount) { + this.onlineDeviceCount = onlineDeviceCount; + } + + public Integer getOfflineDeviceCount() { + return offlineDeviceCount; + } + + public void setOfflineDeviceCount(Integer offlineDeviceCount) { + this.offlineDeviceCount = offlineDeviceCount; + } + + public Integer getGatewayCount() { + return gatewayCount; + } + + public void setGatewayCount(Integer gatewayCount) { + this.gatewayCount = gatewayCount; + } + + public Integer getOnlineGatewayCount() { + return onlineGatewayCount; + } + + public void setOnlineGatewayCount(Integer onlineGatewayCount) { + this.onlineGatewayCount = onlineGatewayCount; + } + + public Integer getOfflineGatewayCount() { + return offlineGatewayCount; + } + + public void setOfflineGatewayCount(Integer offlineGatewayCount) { + this.offlineGatewayCount = offlineGatewayCount; + } + + public String getProjectManager() { + return projectManager; + } + + public void setProjectManager(String projectManager) { + this.projectManager = projectManager; + } + + public BigDecimal getElectricityPrice() { + return electricityPrice; + } + + public void setElectricityPrice(BigDecimal electricityPrice) { + this.electricityPrice = electricityPrice; + } + + @Override + public String getSearchValue() { + return searchValue; + } + + @Override + public void setSearchValue(String searchValue) { + this.searchValue = searchValue; + } + + @Override + public Map getParams() { + return params; + } + + @Override + public void setParams(Map params) { + this.params = params; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("id", id) + .append("projectCode", projectCode) + .append("projectName", projectName) + .append("projectAddress", projectAddress) + .append("installDate", installDate) + .append("acceptanceDate", acceptanceDate) + .append("onlineDate", onlineDate) + .append("deviceCount", deviceCount) + .append("onlineDeviceCount", onlineDeviceCount) + .append("offlineDeviceCount", offlineDeviceCount) + .append("gatewayCount", gatewayCount) + .append("onlineGatewayCount", onlineGatewayCount) + .append("offlineGatewayCount", offlineGatewayCount) + .append("projectManager", projectManager) + .append("electricityPrice", electricityPrice) + .append("searchValue", searchValue) + .append("params", params) + .toString(); + } +} diff --git a/mh-system/src/main/java/com/mh/system/mapper/project/ProjectInfoMapper.java b/mh-system/src/main/java/com/mh/system/mapper/project/ProjectInfoMapper.java new file mode 100644 index 0000000..0399350 --- /dev/null +++ b/mh-system/src/main/java/com/mh/system/mapper/project/ProjectInfoMapper.java @@ -0,0 +1,17 @@ +package com.mh.system.mapper.project; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.mh.common.core.domain.entity.ProjectInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * @author EEMCS + * @version 1.0 + * @project EEMCS + * @description 项目信息管理 + * @date 2026-06-11 + */ +@Mapper +public interface ProjectInfoMapper extends BaseMapper { + +} diff --git a/mh-system/src/main/java/com/mh/system/service/project/IProjectInfoService.java b/mh-system/src/main/java/com/mh/system/service/project/IProjectInfoService.java new file mode 100644 index 0000000..760be91 --- /dev/null +++ b/mh-system/src/main/java/com/mh/system/service/project/IProjectInfoService.java @@ -0,0 +1,55 @@ +package com.mh.system.service.project; + +import com.mh.common.core.domain.entity.ProjectInfo; + +import java.util.List; + +/** + * @author EEMCS + * @version 1.0 + * @project EEMCS + * @description 项目信息管理 + * @date 2026-06-11 + */ +public interface IProjectInfoService { + + /** + * 查询项目信息列表 + * + * @param projectInfo 项目信息 + * @return 项目信息集合 + */ + List selectProjectInfoList(ProjectInfo projectInfo); + + /** + * 根据ID查询项目信息 + * + * @param id 项目ID + * @return 项目信息 + */ + ProjectInfo selectProjectInfoById(String id); + + /** + * 新增项目信息 + * + * @param projectInfo 项目信息 + * @return 结果 + */ + int insertProjectInfo(ProjectInfo projectInfo); + + /** + * 修改项目信息 + * + * @param projectInfo 项目信息 + * @return 结果 + */ + int updateProjectInfo(ProjectInfo projectInfo); + + /** + * 批量删除项目信息 + * + * @param ids 需要删除的项目ID数组 + * @return 结果 + */ + int deleteProjectInfoByIds(String[] ids); +} diff --git a/mh-system/src/main/java/com/mh/system/service/project/impl/ProjectInfoServiceImpl.java b/mh-system/src/main/java/com/mh/system/service/project/impl/ProjectInfoServiceImpl.java new file mode 100644 index 0000000..57441bd --- /dev/null +++ b/mh-system/src/main/java/com/mh/system/service/project/impl/ProjectInfoServiceImpl.java @@ -0,0 +1,120 @@ +package com.mh.system.service.project.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.mh.common.core.domain.entity.ProjectInfo; +import com.mh.common.utils.StringUtils; +import com.mh.system.mapper.project.ProjectInfoMapper; +import com.mh.system.service.project.IProjectInfoService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * @author EEMCS + * @version 1.0 + * @project EEMCS + * @description 项目信息管理 + * @date 2026-06-11 + */ +@Service +public class ProjectInfoServiceImpl implements IProjectInfoService { + + @Resource + private ProjectInfoMapper projectInfoMapper; + + /** + * 查询项目信息列表 + * + * @param projectInfo 项目信息 + * @return 项目信息集合 + */ + @Override + public List selectProjectInfoList(ProjectInfo projectInfo) { + if (projectInfo == null) { + return List.of(); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + + // 项目名称(模糊查询) + if (!StringUtils.isEmpty(projectInfo.getProjectName())) { + queryWrapper.like("project_name", projectInfo.getProjectName()); + } + + // 项目负责人(模糊查询) + if (!StringUtils.isEmpty(projectInfo.getProjectManager())) { + queryWrapper.like("project_manager", projectInfo.getProjectManager()); + } + + // 安装日期范围查询 + if (projectInfo.getParams() != null && !projectInfo.getParams().isEmpty()) { + String beginTimeStr = (String) projectInfo.getParams().get("beginTime"); + String endTimeStr = (String) projectInfo.getParams().get("endTime"); + + if (StringUtils.isNotEmpty(beginTimeStr) && StringUtils.isNotEmpty(endTimeStr)) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate beginTime = LocalDate.parse(beginTimeStr, formatter); + LocalDate endTime = LocalDate.parse(endTimeStr, formatter); + + queryWrapper.between("install_date", java.sql.Date.valueOf(beginTime), java.sql.Date.valueOf(endTime)); + } + } + + // 按创建时间倒序排列 + queryWrapper.orderByDesc("create_time"); + + return projectInfoMapper.selectList(queryWrapper); + } + + /** + * 根据ID查询项目信息 + * + * @param id 项目ID + * @return 项目信息 + */ + @Override + public ProjectInfo selectProjectInfoById(String id) { + return projectInfoMapper.selectById(id); + } + + /** + * 新增项目信息 + * + * @param projectInfo 项目信息 + * @return 结果 + */ + @Override + public int insertProjectInfo(ProjectInfo projectInfo) { + return projectInfoMapper.insert(projectInfo); + } + + /** + * 修改项目信息 + * + * @param projectInfo 项目信息 + * @return 结果 + */ + @Override + public int updateProjectInfo(ProjectInfo projectInfo) { + return projectInfoMapper.updateById(projectInfo); + } + + /** + * 批量删除项目信息 + * + * @param ids 需要删除的项目ID数组 + * @return 结果 + */ + @Override + public int deleteProjectInfoByIds(String[] ids) { + if (ids != null && ids.length > 0) { + for (String id : ids) { + projectInfoMapper.deleteById(id); + } + return ids.length; + } + return 0; + } +}