From 589700dd51c995d5838b278ea362c921e2b6766c Mon Sep 17 00:00:00 2001 From: lingyunxsh Date: Sat, 24 May 2025 09:52:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LogBook.md | 60 +++++++++++++++++++ .../(authenticated)/admin/mqtt-logs/page.tsx | 2 +- .../app/(authenticated)/admin/robots/page.tsx | 10 ++-- charging_web_app/src/utils/axios.ts | 2 +- springboot-init-main/pom.xml | 34 +++++++++++ .../yupi/project/config/SecurityConfig.java | 25 +++++--- .../com/yupi/project/config/WebMvcConfig.java | 30 ++++++++++ .../src/main/resources/application.yml | 4 +- 8 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 springboot-init-main/src/main/java/com/yupi/project/config/WebMvcConfig.java diff --git a/LogBook.md b/LogBook.md index 67c47bb..eed8b99 100644 --- a/LogBook.md +++ b/LogBook.md @@ -147,4 +147,64 @@ * **解决方案**: 恢复原来的依赖数组 `[form, pagination.current, pagination.pageSize]`。虽然这会保留 ESLint 警告,但由于我们已经在 `eslint.config.mjs` 中禁用了相关规则,警告不会影响构建,同时也避免了循环请求问题。 * **操作**: 修改了 `charging_web_app/src/app/(authenticated)/admin/mqtt-logs/page.tsx` 文件。 +* **修复前端 API 路径不一致问题**: + * **问题**: 机器人管理页面 (`admin/robots`) 出现 403 Access Denied 错误,显示 "加载失败: 403 Access Denied"。经排查,发现是 API 请求路径不一致导致的问题。在 `charging_web_app/src/services/api.ts` 中 baseURL 设置为 `/api`,但在 `robots/page.tsx` 中的 API 调用路径没有包含 `/api` 前缀。 + * **原因分析**: 前端配置了两个不同的 axios 实例: + 1. `utils/axios.ts` - baseURL: `process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:7529/api'` + 2. `services/api.ts` - baseURL: `'/api'`(相对路径) + 在 `robots/page.tsx` 中使用了没有 `/api` 前缀的路径,导致请求被发送到错误的 URL。 + * **解决方案**: 修改 `robots/page.tsx` 中的所有 API 请求路径,添加 `/api` 前缀,确保与 `services/api.ts` 中的 baseURL 配置兼容。 + * **操作**: 修改了 `charging_web_app/src/app/(authenticated)/admin/robots/page.tsx` 文件,更新了以下 API 请求路径: + - `/admin/robot/status/types` → `/api/admin/robot/status/types` + - `/admin/robot/list/page` → `/api/admin/robot/list/page` + - `/admin/robot/add` → `/api/admin/robot/add` + - `/admin/robot/delete` → `/api/admin/robot/delete` + - `/admin/robot/update` → `/api/admin/robot/update` + +## 2025-05-26 (基于对话日期推断) + +* **Spring Boot 项目配置文件外部化**: + * **目标**: 将 `application.yml` 从 Spring Boot JAR 包中分离出来,方便在不重新打包的情况下修改配置。 + * **操作**: + 1. 修改了 `springboot-init-main/pom.xml` 文件。 + 2. 在 `spring-boot-maven-plugin` 配置中,通过 `application.yml` 排除了 `application.yml` 文件。 + 3. 添加了 `maven-resources-plugin`,配置其在 `process-resources` 阶段将 `src/main/resources/application.yml` 复制到 `target/config/application.yml`。 + * **预期效果**: 执行 `mvn clean package` 后,`application.yml` 不会包含在 JAR 内,而是会出现在 `target/config/` 目录。应用启动时,可以将此配置文件放在 JAR 同级目录的 `config` 文件夹下,Spring Boot 会自动加载;或通过 `--spring.config.location` 参数指定其路径。 + * **修正 (2025-05-26)**: 发现 `spring-boot-maven-plugin` 中的 `` 参数无效。调整 `pom.xml`: + * 移除了 `spring-boot-maven-plugin` 中的 ``。 + * 在 `` 下添加了 `` 配置,通过 `application.yml` 来确保该文件不被打包进 `target/classes`,从而不进入最终的 JAR。 + * `maven-resources-plugin` 的配置保持不变,继续将 `application.yml` 复制到 `target/config/`。 + +## 2025-05-27 (基于对话日期推断) + +* **修复机器人管理页面 403 Access Denied 错误**: + * **问题**: 机器人管理页面 (`/admin/robots`) 出现 "加载机器人列表失败: 403 Access Denied" 错误。 + * **原因分析**: 经排查,确定是跨域 (CORS) 请求问题。虽然后端已有基本的 CORS 配置,但对于特定的 `/admin/robot/list/page` 接口可能配置不够宽松。 + * **解决方案**: + 1. 增强 `SecurityConfig` 中的 CORS 配置: + * 明确允许所有源 (`setAllowedOriginPatterns("*")`) + * 扩展允许的 HTTP 方法,包括 `PATCH` 和 `HEAD` + * 允许所有请求头 + * 暴露授权相关的响应头 (`Authorization`, `Set-Cookie`, `X-XSRF-TOKEN`) + * 在安全规则中明确添加 `/admin/robot/**` 路径的权限配置 + 2. 添加全局 `WebMvcConfig` 配置类: + * 实现 `WebMvcConfigurer` 接口 + * 通过 `addCorsMappings` 方法为所有请求路径配置 CORS 规则 + * 确保与 `SecurityConfig` 中的 CORS 配置保持一致 + * **预期效果**: 前端页面应能正常访问 `/admin/robot/list/page` 等接口,不再出现 403 Access Denied 错误。 + +* **修复前端 API 请求基础 URL 配置**: + * **问题**: 即使修复了后端 CORS 配置,机器人管理页面仍然显示 "加载机器人列表失败: 403 Access Denied" 错误。 + * **原因分析**: 经排查,发现前端项目存在两个不同的 axios 实例配置: + 1. `charging_web_app/src/services/api.ts` - 使用相对路径 `baseURL: '/api'`,依赖 Next.js 代理或部署时的反向代理。 + 2. `charging_web_app/src/utils/axios.ts` - 使用 `baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:7529/api'`,默认指向本地开发环境。 + 机器人管理页面使用的是第二个实例,导致在服务器部署环境中,请求被发送到 `http://localhost:7529/api`,而不是服务器上的后端地址。 + * **解决方案**: + * 修改 `charging_web_app/src/utils/axios.ts` 中的 `baseURL` 配置,将默认值从 `'http://localhost:7529/api'` 改为 `'/api'`,使其与 `services/api.ts` 中的配置保持一致。 + * 这样,在没有设置 `NEXT_PUBLIC_API_BASE_URL` 环境变量的情况下,请求会默认使用相对路径,依赖 Next.js 的代理配置。 + * **预期效果**: 前端页面应能正常访问 `/admin/robot/list/page` 等接口,不再出现 403 Access Denied 错误。 + * **部署注意事项**: + * 在生产环境中,可以根据需要设置 `NEXT_PUBLIC_API_BASE_URL` 环境变量,指向后端服务器的实际地址。 + * 如果使用相对路径 `/api`,需要确保 Next.js 应用和后端服务部署在同一个域名下,或者通过反向代理(如 Nginx)将 `/api` 路径的请求转发到后端服务。 + --- \ No newline at end of file diff --git a/charging_web_app/src/app/(authenticated)/admin/mqtt-logs/page.tsx b/charging_web_app/src/app/(authenticated)/admin/mqtt-logs/page.tsx index 75fa1bf..57520e8 100644 --- a/charging_web_app/src/app/(authenticated)/admin/mqtt-logs/page.tsx +++ b/charging_web_app/src/app/(authenticated)/admin/mqtt-logs/page.tsx @@ -70,7 +70,7 @@ const MqttCommunicationLogPage: React.FC = () => { } finally { setLoading(false); } - }, [form, pagination.current, pagination.pageSize]); // 恢复原来的依赖,以避免循环依赖 + }, [form, pagination.current, pagination.pageSize]); useEffect(() => { if (user?.role === 'admin') { diff --git a/charging_web_app/src/app/(authenticated)/admin/robots/page.tsx b/charging_web_app/src/app/(authenticated)/admin/robots/page.tsx index d0d23f7..75837d3 100644 --- a/charging_web_app/src/app/(authenticated)/admin/robots/page.tsx +++ b/charging_web_app/src/app/(authenticated)/admin/robots/page.tsx @@ -154,7 +154,7 @@ export default function AdminRobotsPage() { // 获取机器人状态类型 const fetchRobotStatusTypes = useCallback(async () => { try { - const response = await api.get>('/admin/robot/status/types'); + const response = await api.get>('/api/admin/robot/status/types'); if (response.data && response.data.code === 0 && Array.isArray(response.data.data)) { setRobotStatusTypes(response.data.data); // If newRobotData.status is default and status types are loaded, set to the first available status @@ -192,7 +192,7 @@ export default function AdminRobotsPage() { try { // Correctly expect BaseResponse wrapping Page - const response = await api.post>>('/admin/robot/list/page', queryParams); + const response = await api.post>>('/api/admin/robot/list/page', queryParams); if (response.data && response.data.code === 0 && response.data.data) { const pageData = response.data.data; // This is the Page object @@ -274,7 +274,7 @@ export default function AdminRobotsPage() { setIsLoading(true); // Or a specific loading state like setIsAddingRobot(true) try { - const response = await api.post>('/admin/robot/add', newRobotData); + const response = await api.post>('/api/admin/robot/add', newRobotData); if (response.data && response.data.code === 0) { setIsAddModalOpen(false); fetchRobots(pageInfo.current, pageInfo.size, filters); // Refresh list @@ -306,7 +306,7 @@ export default function AdminRobotsPage() { setDeleteError(null); try { const deletePayload: DeleteRobotRequest = { id: robotToDelete.id }; - const response = await api.post>('/admin/robot/delete', deletePayload); + const response = await api.post>('/api/admin/robot/delete', deletePayload); if (response.data && response.data.code === 0 && response.data.data === true) { setIsDeleteModalOpen(false); setRobotToDelete(null); @@ -374,7 +374,7 @@ export default function AdminRobotsPage() { batteryLevel: robotToEditData.batteryLevel, currentLocation: robotToEditData.currentLocation, }; - const response = await api.post>('/admin/robot/update', payload); + const response = await api.post>('/api/admin/robot/update', payload); if (response.data && response.data.code === 0) { setIsEditModalOpen(false); fetchRobots(pageInfo.current, pageInfo.size, filters); // Refresh list diff --git a/charging_web_app/src/utils/axios.ts b/charging_web_app/src/utils/axios.ts index a4fa93b..366fcf9 100644 --- a/charging_web_app/src/utils/axios.ts +++ b/charging_web_app/src/utils/axios.ts @@ -1,7 +1,7 @@ import axios from 'axios'; const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:7529/api', + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, headers: { 'Content-Type': 'application/json', }, diff --git a/springboot-init-main/pom.xml b/springboot-init-main/pom.xml index 3dd5115..6a7e148 100644 --- a/springboot-init-main/pom.xml +++ b/springboot-init-main/pom.xml @@ -104,7 +104,16 @@ + + + + + + + + + org.springframework.boot @@ -126,6 +135,31 @@ + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + copy-resources + process-resources + + copy-resources + + + ${project.build.directory}/config + + + src/main/resources + + application.yml + + + + + + + diff --git a/springboot-init-main/src/main/java/com/yupi/project/config/SecurityConfig.java b/springboot-init-main/src/main/java/com/yupi/project/config/SecurityConfig.java index 35a9d19..302540c 100644 --- a/springboot-init-main/src/main/java/com/yupi/project/config/SecurityConfig.java +++ b/springboot-init-main/src/main/java/com/yupi/project/config/SecurityConfig.java @@ -14,6 +14,7 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; +import java.util.Collections; /** * 安全相关配置 (集成CORS) @@ -46,6 +47,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers(HttpMethod.DELETE, "/user/delete/**").hasAuthority(UserRoleEnum.ADMIN.getValue()) .antMatchers(HttpMethod.POST, "/user/admin/add").hasAuthority(UserRoleEnum.ADMIN.getValue()) .antMatchers(HttpMethod.PUT, "/user/admin/update").hasAuthority(UserRoleEnum.ADMIN.getValue()) + // 明确允许 /admin/robot 相关请求 + .antMatchers("/admin/robot/**").hasAuthority(UserRoleEnum.ADMIN.getValue()) // 其他所有请求都需要认证 .anyRequest().authenticated() ) @@ -58,16 +61,22 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - // TODO: 在生产环境请显式指定允许的源,而不是 "*" - configuration.setAllowedOriginPatterns(Arrays.asList("*")); // 允许所有源模式 - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); - configuration.setAllowedHeaders(Arrays.asList("*")); // 允许所有头 - configuration.setAllowCredentials(true); // 允许发送 Cookie - configuration.setMaxAge(3600L); // 预检请求的有效期 - // configuration.setExposedHeaders(Arrays.asList("YourCustomHeader")); // 如果需要暴露自定义头 + // 允许所有源,更宽松的配置 + configuration.setAllowedOriginPatterns(Collections.singletonList("*")); + // 允许所有HTTP方法 + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD")); + // 允许所有头 + configuration.setAllowedHeaders(Collections.singletonList("*")); + // 允许发送凭证(cookies、HTTP认证及客户端SSL证明等) + configuration.setAllowCredentials(true); + // 预检请求的有效期,单位为秒 + configuration.setMaxAge(3600L); + // 暴露响应头,特别是授权相关的 + configuration.setExposedHeaders(Arrays.asList("Authorization", "Set-Cookie", "X-XSRF-TOKEN")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); // 对所有路径应用配置 + // 对所有路径应用配置 + source.registerCorsConfiguration("/**", configuration); return source; } } \ No newline at end of file diff --git a/springboot-init-main/src/main/java/com/yupi/project/config/WebMvcConfig.java b/springboot-init-main/src/main/java/com/yupi/project/config/WebMvcConfig.java new file mode 100644 index 0000000..b803744 --- /dev/null +++ b/springboot-init-main/src/main/java/com/yupi/project/config/WebMvcConfig.java @@ -0,0 +1,30 @@ +package com.yupi.project.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 全局跨域配置 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 覆盖所有请求 + registry.addMapping("/**") + // 允许发送Cookie + .allowCredentials(true) + // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) + .allowedOriginPatterns("*") + // 放行哪些请求方式 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH") + // 放行哪些请求头 + .allowedHeaders("*") + // 暴露哪些请求头 + .exposedHeaders("Authorization", "Set-Cookie", "X-XSRF-TOKEN") + // 预检请求的有效期,单位为秒 + .maxAge(3600); + } +} \ No newline at end of file diff --git a/springboot-init-main/src/main/resources/application.yml b/springboot-init-main/src/main/resources/application.yml index f4b64ff..19af3d9 100644 --- a/springboot-init-main/src/main/resources/application.yml +++ b/springboot-init-main/src/main/resources/application.yml @@ -4,9 +4,9 @@ spring: # DataSource Config datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://yuyun-us1.stormrain.cn:3306/mqtt_power?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + url: jdbc:mysql://yuyun-hk1.stormrain.cn:3306/mqtt_power?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root - password: mysql_a4MQ4P + password: mysql_3wdCbm mvc: pathmatch: matching-strategy: ANT_PATH_MATCHER