292 lines
6.8 KiB
Markdown
292 lines
6.8 KiB
Markdown
|
|
# Banner管理API Bug修复报告
|
|||
|
|
|
|||
|
|
## 🐛 问题描述
|
|||
|
|
|
|||
|
|
### 错误1: 批量排序验证失败
|
|||
|
|
```
|
|||
|
|
PUT /admin/banners/batch-sort
|
|||
|
|
HandlerMethodValidationException: 400 BAD_REQUEST "Validation failure"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**根本原因**:
|
|||
|
|
- 前端发送的数据格式与`BannerUpdateDto`的验证要求不匹配
|
|||
|
|
- `BannerUpdateDto`包含过多必填字段,而批量排序只需要`id`和`sortOrder`
|
|||
|
|
|
|||
|
|
### 错误2: 状态切换接口不存在
|
|||
|
|
```
|
|||
|
|
PUT /admin/banners/status
|
|||
|
|
HttpRequestMethodNotSupportedException: Request method 'PUT' is not supported
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**根本原因**:
|
|||
|
|
- 控制器中缺少`/status`接口
|
|||
|
|
- 前端调用的接口在后端没有实现
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 修复方案
|
|||
|
|
|
|||
|
|
### 1. 创建专用DTO
|
|||
|
|
|
|||
|
|
#### 新增 `BannerSortDto.java`
|
|||
|
|
```java
|
|||
|
|
@Data
|
|||
|
|
@Schema(description = "Banner排序请求")
|
|||
|
|
public class BannerSortDto {
|
|||
|
|
@NotNull(message = "Banner ID不能为空")
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
@NotNull(message = "排序值不能为空")
|
|||
|
|
@Min(value = 0, message = "排序值不能小于0")
|
|||
|
|
private Integer sortOrder;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 新增 `BannerStatusDto.java`
|
|||
|
|
```java
|
|||
|
|
@Data
|
|||
|
|
@Schema(description = "Banner状态请求")
|
|||
|
|
public class BannerStatusDto {
|
|||
|
|
@NotNull(message = "Banner ID不能为空")
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
@NotNull(message = "状态不能为空")
|
|||
|
|
private Boolean isEnabled;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 更新控制器接口
|
|||
|
|
|
|||
|
|
#### 修改批量排序接口
|
|||
|
|
```java
|
|||
|
|
@PutMapping("/batch-sort")
|
|||
|
|
public Result<Void> batchUpdateSortOrder(@Valid @RequestBody List<BannerSortDto> sortDtos) {
|
|||
|
|
log.info("管理员批量更新Banner排序: size={}", sortDtos.size());
|
|||
|
|
bannerService.batchUpdateSortOrder(sortDtos);
|
|||
|
|
return Result.success(null);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 新增状态切换接口
|
|||
|
|
```java
|
|||
|
|
@PutMapping("/status")
|
|||
|
|
public Result<Void> updateBannerStatus(@Valid @RequestBody BannerStatusDto statusDto) {
|
|||
|
|
log.info("管理员更新Banner状态: id={}, enabled={}", statusDto.getId(), statusDto.getIsEnabled());
|
|||
|
|
bannerService.updateBannerStatus(statusDto.getId(), statusDto.getIsEnabled());
|
|||
|
|
return Result.success(null);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 更新服务层
|
|||
|
|
|
|||
|
|
#### 修改服务接口
|
|||
|
|
```java
|
|||
|
|
// 修改批量排序方法签名
|
|||
|
|
void batchUpdateSortOrder(List<BannerSortDto> sortDtos);
|
|||
|
|
|
|||
|
|
// 新增状态更新方法
|
|||
|
|
void updateBannerStatus(Long id, Boolean isEnabled);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 更新服务实现
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
@Transactional
|
|||
|
|
public void batchUpdateSortOrder(List<BannerSortDto> sortDtos) {
|
|||
|
|
if (sortDtos == null || sortDtos.isEmpty()) {
|
|||
|
|
throw new BusinessException("批量更新数据不能为空");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
List<Banner> banners = sortDtos.stream().map(sortDto -> {
|
|||
|
|
Banner banner = new Banner();
|
|||
|
|
banner.setId(sortDto.getId());
|
|||
|
|
banner.setSortOrder(sortDto.getSortOrder());
|
|||
|
|
banner.setUpdateTime(LocalDateTime.now());
|
|||
|
|
return banner;
|
|||
|
|
}).toList();
|
|||
|
|
|
|||
|
|
int result = bannerMapper.batchUpdateSortOrder(banners);
|
|||
|
|
if (result <= 0) {
|
|||
|
|
throw new BusinessException("批量更新排序失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log.info("批量更新Banner排序成功: size={}", sortDtos.size());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
@Transactional
|
|||
|
|
public void updateBannerStatus(Long id, Boolean isEnabled) {
|
|||
|
|
Banner existingBanner = bannerMapper.selectById(id);
|
|||
|
|
if (existingBanner == null) {
|
|||
|
|
throw new BusinessException("Banner不存在");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Banner banner = new Banner();
|
|||
|
|
banner.setId(id);
|
|||
|
|
banner.setIsEnabled(Boolean.TRUE.equals(isEnabled) ? 1 : 0);
|
|||
|
|
banner.setUpdateTime(LocalDateTime.now());
|
|||
|
|
|
|||
|
|
int result = bannerMapper.updateById(banner);
|
|||
|
|
if (result <= 0) {
|
|||
|
|
throw new BusinessException("更新Banner状态失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log.info("更新Banner状态成功: id={}, enabled={}", id, isEnabled);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试验证
|
|||
|
|
|
|||
|
|
### 测试页面
|
|||
|
|
创建了 `test_banner_admin.html` 测试页面,包含:
|
|||
|
|
- 📄 Banner列表加载
|
|||
|
|
- 🔢 批量排序测试
|
|||
|
|
- 🔄 状态切换测试
|
|||
|
|
- 📊 实时结果显示
|
|||
|
|
|
|||
|
|
### 测试用例
|
|||
|
|
|
|||
|
|
#### 1. 批量排序测试
|
|||
|
|
```javascript
|
|||
|
|
// 请求数据格式
|
|||
|
|
PUT /admin/banners/batch-sort
|
|||
|
|
[
|
|||
|
|
{"id": 1, "sortOrder": 1},
|
|||
|
|
{"id": 2, "sortOrder": 2},
|
|||
|
|
{"id": 3, "sortOrder": 3}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// 预期响应
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "操作成功",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 状态切换测试
|
|||
|
|
```javascript
|
|||
|
|
// 请求数据格式
|
|||
|
|
PUT /admin/banners/status
|
|||
|
|
{
|
|||
|
|
"id": 1,
|
|||
|
|
"isEnabled": false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 预期响应
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "操作成功",
|
|||
|
|
"data": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 修复前后对比
|
|||
|
|
|
|||
|
|
### 修复前
|
|||
|
|
| 接口路径 | 状态 | 问题 |
|
|||
|
|
|---------|------|------|
|
|||
|
|
| `PUT /admin/banners/batch-sort` | ❌ 失败 | 参数验证失败 |
|
|||
|
|
| `PUT /admin/banners/status` | ❌ 失败 | 接口不存在 |
|
|||
|
|
|
|||
|
|
### 修复后
|
|||
|
|
| 接口路径 | 状态 | 功能 |
|
|||
|
|
|---------|------|------|
|
|||
|
|
| `PUT /admin/banners/batch-sort` | ✅ 正常 | 批量更新排序 |
|
|||
|
|
| `PUT /admin/banners/status` | ✅ 正常 | 状态切换 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛡️ 安全性
|
|||
|
|
|
|||
|
|
### 权限验证
|
|||
|
|
- ✅ 所有接口都使用 `@RequireAdminOrStaff` 注解
|
|||
|
|
- ✅ 需要有效的管理员JWT Token
|
|||
|
|
- ✅ 自动记录操作日志
|
|||
|
|
|
|||
|
|
### 数据验证
|
|||
|
|
- ✅ 使用专用DTO进行参数验证
|
|||
|
|
- ✅ 数据库操作前检查记录存在性
|
|||
|
|
- ✅ 事务保护确保数据一致性
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 使用指南
|
|||
|
|
|
|||
|
|
### 前端调用示例
|
|||
|
|
|
|||
|
|
#### 批量排序
|
|||
|
|
```javascript
|
|||
|
|
const sortData = [
|
|||
|
|
{id: 1, sortOrder: 1},
|
|||
|
|
{id: 2, sortOrder: 2}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const response = await fetch('/admin/banners/batch-sort', {
|
|||
|
|
method: 'PUT',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${adminToken}`,
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(sortData)
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 状态切换
|
|||
|
|
```javascript
|
|||
|
|
const statusData = {
|
|||
|
|
id: 1,
|
|||
|
|
isEnabled: false
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const response = await fetch('/admin/banners/status', {
|
|||
|
|
method: 'PUT',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${adminToken}`,
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(statusData)
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 文件清单
|
|||
|
|
|
|||
|
|
### 新增文件
|
|||
|
|
- ✅ `src/main/java/com/dora/dto/BannerSortDto.java`
|
|||
|
|
- ✅ `src/main/java/com/dora/dto/BannerStatusDto.java`
|
|||
|
|
- ✅ `src/main/resources/static/test_banner_admin.html`
|
|||
|
|
|
|||
|
|
### 修改文件
|
|||
|
|
- ✅ `src/main/java/com/dora/controller/AdminBannerController.java`
|
|||
|
|
- ✅ `src/main/java/com/dora/service/BannerService.java`
|
|||
|
|
- ✅ `src/main/java/com/dora/service/impl/BannerServiceImpl.java`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 总结
|
|||
|
|
|
|||
|
|
### ✅ 修复成果
|
|||
|
|
1. **参数验证优化**: 创建专用DTO,避免过度验证
|
|||
|
|
2. **接口完整性**: 补充缺失的状态切换接口
|
|||
|
|
3. **错误处理**: 增强异常处理和日志记录
|
|||
|
|
4. **测试支持**: 提供完整的测试页面
|
|||
|
|
|
|||
|
|
### 🚨 注意事项
|
|||
|
|
1. **参数格式**: 确保前端发送的数据格式与DTO要求一致
|
|||
|
|
2. **权限验证**: 所有操作都需要管理员权限
|
|||
|
|
3. **数据一致性**: 批量操作使用事务保护
|
|||
|
|
4. **错误处理**: 详细的错误信息便于问题排查
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**修复状态**: ✅ 已完成
|
|||
|
|
**测试状态**: ✅ 已验证
|
|||
|
|
**风险等级**: 低(不影响现有功能)
|
|||
|
|
**部署要求**: 重启服务器使修改生效
|