修复bug
This commit is contained in:
60
LogBook.md
60
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` 配置中,通过 `<excludeFiles>application.yml</excludeFiles>` 排除了 `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` 中的 `<excludeFiles>` 参数无效。调整 `pom.xml`:
|
||||
* 移除了 `spring-boot-maven-plugin` 中的 `<excludeFiles>`。
|
||||
* 在 `<build>` 下添加了 `<resources>` 配置,通过 `<exclude>application.yml</exclude>` 来确保该文件不被打包进 `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` 路径的请求转发到后端服务。
|
||||
|
||||
---
|
||||
@@ -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') {
|
||||
|
||||
@@ -154,7 +154,7 @@ export default function AdminRobotsPage() {
|
||||
// 获取机器人状态类型
|
||||
const fetchRobotStatusTypes = useCallback(async () => {
|
||||
try {
|
||||
const response = await api.get<BaseResponse<string[]>>('/admin/robot/status/types');
|
||||
const response = await api.get<BaseResponse<string[]>>('/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<ChargingRobot>
|
||||
const response = await api.post<BaseResponse<Page<ChargingRobot>>>('/admin/robot/list/page', queryParams);
|
||||
const response = await api.post<BaseResponse<Page<ChargingRobot>>>('/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<ChargingRobot> 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<BaseResponse<ChargingRobot>>('/admin/robot/add', newRobotData);
|
||||
const response = await api.post<BaseResponse<ChargingRobot>>('/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<BaseResponse<boolean>>('/admin/robot/delete', deletePayload);
|
||||
const response = await api.post<BaseResponse<boolean>>('/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<BaseResponse<null>>('/admin/robot/update', payload);
|
||||
const response = await api.post<BaseResponse<null>>('/api/admin/robot/update', payload);
|
||||
if (response.data && response.data.code === 0) {
|
||||
setIsEditModalOpen(false);
|
||||
fetchRobots(pageInfo.current, pageInfo.size, filters); // Refresh list
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -104,7 +104,16 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- 注意修改!!! -->
|
||||
<build>
|
||||
<!-- <resources>-->
|
||||
<!-- <resource>-->
|
||||
<!-- <directory>src/main/resources</directory>-->
|
||||
<!-- <excludes>-->
|
||||
<!-- <exclude>application.yml</exclude>-->
|
||||
<!-- </excludes>-->
|
||||
<!-- </resource>-->
|
||||
<!-- </resources>-->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -126,6 +135,31 @@
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/config</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>application.yml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user