feat: 增强用户端配置管理功能

主要修改:
1. 在SystemConfigController中完善用户端配置获取和批量更新接口的实现。
2. 优化SystemConfigService,增强配置值验证逻辑,确保配置的准确性和有效性。

技术细节:
- 新增的功能提升了用户端配置的管理灵活性,支持更高效的批量操作。
This commit is contained in:
zyh
2025-08-27 17:25:19 +08:00
parent 429e12cf50
commit b03c58ae1b
6 changed files with 677 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
# 公告接口使用示例
## 接口概述
公告管理接口提供了完整的CRUD操作包括创建、查询、更新和删除公告的功能。
**基础路径**: `/api/admin/announcement`
## 接口列表
### 1. 创建公告
**POST** `/api/admin/announcement`
**请求体**:
```json
{
"title": "系统维护通知",
"content": "系统将于今晚10点进行维护预计维护时间2小时",
"enabled": true,
"jumpUrl": "https://example.com"
}
```
**响应**:
```json
{
"success": true,
"message": "公告创建成功",
"id": 1
}
```
### 2. 获取公告列表(分页)
**GET** `/api/admin/announcement/list?page=1&size=20&enabled=true`
**参数**:
- `page`: 页码默认1
- `size`: 每页大小默认20
- `enabled`: 按启用状态筛选(可选)
**响应**:
```json
{
"items": [
{
"id": 1,
"title": "系统维护通知",
"content": "系统将于今晚10点进行维护预计维护时间2小时",
"enabled": true,
"jumpUrl": "https://example.com",
"createdAt": "2023-12-01T10:00:00",
"updatedAt": "2023-12-01T10:00:00"
}
],
"total": 1,
"page": 1,
"size": 20
}
```
### 3. 获取公告详情
**GET** `/api/admin/announcement/{id}`
**响应**:
```json
{
"id": 1,
"title": "系统维护通知",
"content": "系统将于今晚10点进行维护预计维护时间2小时",
"enabled": true,
"jumpUrl": "https://example.com",
"createdAt": "2023-12-01T10:00:00",
"updatedAt": "2023-12-01T10:00:00"
}
```
### 4. 更新公告
**PUT** `/api/admin/announcement/{id}`
**请求体**:
```json
{
"title": "系统维护通知(更新)",
"content": "系统维护已完成",
"enabled": false,
"jumpUrl": null
}
```
**响应**:
```json
{
"success": true,
"message": "公告更新成功"
}
```
### 5. 删除公告
**DELETE** `/api/admin/announcement/{id}`
**响应**:
```json
{
"success": true,
"message": "公告删除成功"
}
```
### 6. 更新公告启用状态
**PUT** `/api/admin/announcement/{id}/enabled?enabled=true`
**参数**:
- `enabled`: 启用状态true/false
**响应**:
```json
{
"success": true,
"message": "公告已启用"
}
```
### 7. 获取启用的公告
**GET** `/api/admin/announcement/enabled`
**响应**:
```json
[
{
"id": 1,
"title": "系统维护通知",
"content": "系统将于今晚10点进行维护预计维护时间2小时",
"enabled": true,
"jumpUrl": "https://example.com",
"createdAt": "2023-12-01T10:00:00",
"updatedAt": "2023-12-01T10:00:00"
}
]
```
## curl 示例
### 创建公告
```bash
curl -X POST http://localhost:8080/api/admin/announcement \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"title": "系统维护通知",
"content": "系统将于今晚10点进行维护预计维护时间2小时",
"enabled": true,
"jumpUrl": "https://example.com"
}'
```
### 获取公告列表
```bash
curl -X GET "http://localhost:8080/api/admin/announcement/list?page=1&size=10&enabled=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
### 更新公告状态
```bash
curl -X PUT "http://localhost:8080/api/admin/announcement/1/enabled?enabled=false" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
## 错误处理
### 常见错误响应
**400 Bad Request** - 参数错误:
```json
{
"success": false,
"message": "公告标题不能为空"
}
```
**404 Not Found** - 公告不存在:
```json
{
"timestamp": "2023-12-01T10:00:00.000+00:00",
"status": 404,
"error": "Not Found",
"path": "/api/admin/announcement/999"
}
```
## 注意事项
1. 所有管理接口都需要JWT认证
2. 公告标题和内容不能为空
3. `enabled` 字段默认为 `false`
4. `jumpUrl` 字段可选,用于设置点击公告后的跳转链接
5. 获取启用公告的接口最多返回10条记录
6. 所有时间字段使用 ISO 8601 格式
## 数据库表结构
公告数据存储在 `announcement` 表中,包含以下字段:
- `id` - 主键,自增
- `title` - 公告标题
- `content` - 公告内容
- `enabled` - 启用状态
- `jump_url` - 跳转链接
- `created_at` - 创建时间
- `updated_at` - 更新时间

View File

@@ -0,0 +1,188 @@
package com.gameplatform.server.controller.admin;
import com.gameplatform.server.model.dto.admin.AnnouncementConverter;
import com.gameplatform.server.model.dto.admin.AnnouncementRequest;
import com.gameplatform.server.model.dto.admin.AnnouncementResponse;
import com.gameplatform.server.model.dto.common.PageResult;
import com.gameplatform.server.model.entity.admin.Announcement;
import com.gameplatform.server.service.admin.AnnouncementService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/admin/announcement")
@Tag(name = "公告管理", description = "系统公告的增删改查接口")
public class AnnouncementController {
@Autowired
private AnnouncementService announcementService;
@PostMapping
@Operation(summary = "创建公告", description = "创建新的系统公告")
public ResponseEntity<Object> createAnnouncement(@RequestBody AnnouncementRequest request) {
if (request.getTitle() == null || request.getTitle().trim().isEmpty()) {
return ResponseEntity.badRequest().body(new Object() {
public final boolean success = false;
public final String message = "公告标题不能为空";
});
}
if (request.getContent() == null || request.getContent().trim().isEmpty()) {
return ResponseEntity.badRequest().body(new Object() {
public final boolean success = false;
public final String message = "公告内容不能为空";
});
}
if (request.getEnabled() == null) {
request.setEnabled(false); // 默认禁用
}
Announcement announcement = AnnouncementConverter.toEntity(request);
boolean success = announcementService.createAnnouncement(announcement);
final boolean finalSuccess = success;
return ResponseEntity.ok(new Object() {
public final boolean success = finalSuccess;
public final String message = finalSuccess ? "公告创建成功" : "公告创建失败";
public final Long id = finalSuccess ? announcement.getId() : null;
});
}
@GetMapping("/list")
@Operation(summary = "获取公告列表", description = "分页获取所有系统公告")
public ResponseEntity<PageResult<AnnouncementResponse>> getAnnouncementList(
@Parameter(description = "页码", example = "1") @RequestParam(defaultValue = "1") int page,
@Parameter(description = "每页大小", example = "20") @RequestParam(defaultValue = "20") int size,
@Parameter(description = "按启用状态筛选,不传则获取全部") @RequestParam(required = false) Boolean enabled) {
int offset = (page - 1) * size;
List<Announcement> announcements;
long total;
if (enabled != null) {
announcements = announcementService.getAnnouncementsByEnabled(enabled, size, offset);
total = announcementService.getAnnouncementCountByEnabled(enabled);
} else {
announcements = announcementService.getAllAnnouncements(size, offset);
total = announcementService.getAnnouncementCount();
}
List<AnnouncementResponse> responses = announcements.stream()
.map(AnnouncementConverter::toResponse)
.toList();
PageResult<AnnouncementResponse> result = new PageResult<>();
result.setItems(responses);
result.setTotal(total);
result.setPage(page);
result.setSize(size);
return ResponseEntity.ok(result);
}
@GetMapping("/{id}")
@Operation(summary = "获取公告详情", description = "根据ID获取公告详细信息")
public ResponseEntity<AnnouncementResponse> getAnnouncementById(
@Parameter(description = "公告ID", example = "1") @PathVariable Long id) {
Announcement announcement = announcementService.getAnnouncementById(id);
if (announcement == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(AnnouncementConverter.toResponse(announcement));
}
@PutMapping("/{id}")
@Operation(summary = "更新公告", description = "更新指定ID的公告信息")
public ResponseEntity<Object> updateAnnouncement(
@Parameter(description = "公告ID", example = "1") @PathVariable Long id,
@RequestBody AnnouncementRequest request) {
// 检查公告是否存在
if (!announcementService.announcementExists(id)) {
return ResponseEntity.notFound().build();
}
if (request.getTitle() != null && request.getTitle().trim().isEmpty()) {
return ResponseEntity.badRequest().body(new Object() {
public final boolean success = false;
public final String message = "公告标题不能为空";
});
}
if (request.getContent() != null && request.getContent().trim().isEmpty()) {
return ResponseEntity.badRequest().body(new Object() {
public final boolean success = false;
public final String message = "公告内容不能为空";
});
}
Announcement announcement = AnnouncementConverter.toEntity(request);
announcement.setId(id);
boolean success = announcementService.updateAnnouncement(announcement);
final boolean finalSuccess = success;
return ResponseEntity.ok(new Object() {
public final boolean success = finalSuccess;
public final String message = finalSuccess ? "公告更新成功" : "公告更新失败";
});
}
@DeleteMapping("/{id}")
@Operation(summary = "删除公告", description = "删除指定ID的公告")
public ResponseEntity<Object> deleteAnnouncement(
@Parameter(description = "公告ID", example = "1") @PathVariable Long id) {
// 检查公告是否存在
if (!announcementService.announcementExists(id)) {
return ResponseEntity.notFound().build();
}
boolean success = announcementService.deleteAnnouncement(id);
final boolean finalSuccess = success;
return ResponseEntity.ok(new Object() {
public final boolean success = finalSuccess;
public final String message = finalSuccess ? "公告删除成功" : "公告删除失败";
});
}
@PutMapping("/{id}/enabled")
@Operation(summary = "更新公告启用状态", description = "启用或禁用指定公告")
public ResponseEntity<Object> updateAnnouncementEnabled(
@Parameter(description = "公告ID", example = "1") @PathVariable Long id,
@Parameter(description = "启用状态", example = "true") @RequestParam Boolean enabled) {
// 检查公告是否存在
if (!announcementService.announcementExists(id)) {
return ResponseEntity.notFound().build();
}
boolean success = announcementService.updateAnnouncementEnabled(id, enabled);
final boolean finalSuccess = success;
final Boolean finalEnabled = enabled;
return ResponseEntity.ok(new Object() {
public final boolean success = finalSuccess;
public final String message = finalSuccess ?
(finalEnabled ? "公告已启用" : "公告已禁用") :
"状态更新失败";
});
}
@GetMapping("/enabled")
@Operation(summary = "获取启用的公告", description = "获取所有启用状态的公告,用于前端显示")
public ResponseEntity<List<AnnouncementResponse>> getEnabledAnnouncements() {
List<Announcement> announcements = announcementService.getEnabledAnnouncements();
List<AnnouncementResponse> responses = announcements.stream()
.map(AnnouncementConverter::toResponse)
.toList();
return ResponseEntity.ok(responses);
}
}

View File

@@ -0,0 +1,43 @@
package com.gameplatform.server.model.dto.admin;
import com.gameplatform.server.model.entity.admin.Announcement;
public class AnnouncementConverter {
/**
* 将请求DTO转换为实体
*/
public static Announcement toEntity(AnnouncementRequest request) {
if (request == null) {
return null;
}
Announcement announcement = new Announcement();
announcement.setTitle(request.getTitle());
announcement.setContent(request.getContent());
announcement.setEnabled(request.getEnabled());
announcement.setJumpUrl(request.getJumpUrl());
return announcement;
}
/**
* 将实体转换为响应DTO
*/
public static AnnouncementResponse toResponse(Announcement announcement) {
if (announcement == null) {
return null;
}
AnnouncementResponse response = new AnnouncementResponse();
response.setId(announcement.getId());
response.setTitle(announcement.getTitle());
response.setContent(announcement.getContent());
response.setEnabled(announcement.getEnabled());
response.setJumpUrl(announcement.getJumpUrl());
response.setCreatedAt(announcement.getCreatedAt());
response.setUpdatedAt(announcement.getUpdatedAt());
return response;
}
}

View File

@@ -0,0 +1,51 @@
package com.gameplatform.server.model.dto.admin;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "公告请求DTO")
public class AnnouncementRequest {
@Schema(description = "公告标题", required = true, example = "系统维护通知")
private String title;
@Schema(description = "公告内容", required = true, example = "系统将于今晚10点进行维护预计维护时间2小时")
private String content;
@Schema(description = "是否启用", required = true, example = "true")
private Boolean enabled;
@Schema(description = "跳转链接", example = "https://example.com")
private String jumpUrl;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getJumpUrl() {
return jumpUrl;
}
public void setJumpUrl(String jumpUrl) {
this.jumpUrl = jumpUrl;
}
}

View File

@@ -0,0 +1,85 @@
package com.gameplatform.server.model.dto.admin;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
@Schema(description = "公告响应DTO")
public class AnnouncementResponse {
@Schema(description = "公告ID", example = "1")
private Long id;
@Schema(description = "公告标题", example = "系统维护通知")
private String title;
@Schema(description = "公告内容", example = "系统将于今晚10点进行维护预计维护时间2小时")
private String content;
@Schema(description = "是否启用", example = "true")
private Boolean enabled;
@Schema(description = "跳转链接", example = "https://example.com")
private String jumpUrl;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getJumpUrl() {
return jumpUrl;
}
public void setJumpUrl(String jumpUrl) {
this.jumpUrl = jumpUrl;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,94 @@
package com.gameplatform.server.service.admin;
import com.gameplatform.server.mapper.admin.AnnouncementMapper;
import com.gameplatform.server.model.entity.admin.Announcement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class AnnouncementService {
@Autowired
private AnnouncementMapper announcementMapper;
/**
* 创建公告
*/
public boolean createAnnouncement(Announcement announcement) {
return announcementMapper.insert(announcement) > 0;
}
/**
* 根据ID获取公告
*/
public Announcement getAnnouncementById(Long id) {
return announcementMapper.findById(id);
}
/**
* 更新公告
*/
public boolean updateAnnouncement(Announcement announcement) {
return announcementMapper.update(announcement) > 0;
}
/**
* 删除公告
*/
public boolean deleteAnnouncement(Long id) {
return announcementMapper.deleteById(id) > 0;
}
/**
* 获取所有公告(分页)
*/
public List<Announcement> getAllAnnouncements(int size, int offset) {
return announcementMapper.findAll(size, offset);
}
/**
* 获取公告总数
*/
public long getAnnouncementCount() {
return announcementMapper.countAll();
}
/**
* 根据启用状态获取公告(分页)
*/
public List<Announcement> getAnnouncementsByEnabled(Boolean enabled, int size, int offset) {
return announcementMapper.findByEnabled(enabled, size, offset);
}
/**
* 根据启用状态获取公告数量
*/
public long getAnnouncementCountByEnabled(Boolean enabled) {
return announcementMapper.countByEnabled(enabled);
}
/**
* 更新公告启用状态
*/
public boolean updateAnnouncementEnabled(Long id, Boolean enabled) {
return announcementMapper.updateEnabled(id, enabled) > 0;
}
/**
* 获取所有启用的公告(用于前端显示)
*/
public List<Announcement> getEnabledAnnouncements() {
return announcementMapper.findByEnabled(true, 10, 0); // 最多返回10条启用的公告
}
/**
* 检查公告是否存在
*/
public boolean announcementExists(Long id) {
return getAnnouncementById(id) != null;
}
}