diff --git a/docs/系统配置接口使用示例.md b/docs/系统配置接口使用示例.md new file mode 100644 index 0000000..4040491 --- /dev/null +++ b/docs/系统配置接口使用示例.md @@ -0,0 +1,237 @@ +# 系统配置接口使用示例 + +## 接口概览 + +系统配置接口提供了完整的CRUD操作,支持系统参数的管理和配置。基础路径:`/api/admin/config` + +## 1. 基础配置管理 + +### 获取配置列表 +```http +GET /api/admin/config/list?page=1&size=20 +``` + +响应示例: +```json +{ + "items": [ + { + "id": 1, + "configKey": "link.default_quantity", + "configValue": "50", + "configType": "INTEGER", + "description": "链接生成默认奖励点数", + "isSystem": true, + "createdAt": "2025-08-25T14:19:33.974", + "updatedAt": "2025-08-25T14:19:33.974" + } + ], + "total": 12, + "page": 1, + "size": 20 +} +``` + +### 根据键获取配置 +```http +GET /api/admin/config/key/link.default_quantity +``` + +### 根据类型获取配置 +```http +GET /api/admin/config/type/INTEGER +``` + +### 创建配置 +```http +POST /api/admin/config +Content-Type: application/json + +{ + "configKey": "new.config.key", + "configValue": "new value", + "configType": "STRING", + "description": "新的配置项", + "isSystem": false +} +``` + +### 更新配置 +```http +PUT /api/admin/config/1 +Content-Type: application/json + +{ + "configKey": "link.default_quantity", + "configValue": "100", + "configType": "INTEGER", + "description": "链接生成默认奖励点数", + "isSystem": true +} +``` + +### 删除配置 +```http +DELETE /api/admin/config/1 +``` + +### 根据键删除配置 +```http +DELETE /api/admin/config/key/test.config +``` + +## 2. 专门配置获取接口 + +### 获取链接默认配置 +```http +GET /api/admin/config/link/defaults +``` + +响应示例: +```json +{ + "defaultQuantity": 50, + "refreshInterval": 300, + "qrExpireTime": 14400, + "maxTimesPerBatch": 100, + "minQuantity": 10, + "maxQuantity": 1000 +} +``` + +### 获取脚本配置 +```http +GET /api/admin/config/script/config +``` + +响应示例: +```json +{ + "serverUrl": "http://36.138.184.60:12345", + "qrPathTemplate": "/{machineId}/二维码.png" +} +``` + +### 获取用户端配置 +```http +GET /api/admin/config/user/config +``` + +响应示例: +```json +{ + "qrExpireSeconds": 60, + "refreshWaitSeconds": 10, + "linkExpireHours": 24, + "assetsBaseUrl": "http://36.138.184.60:12345" +} +``` + +## 3. 批量操作接口 + +### 批量更新配置 +```http +POST /api/admin/config/batch +Content-Type: application/json + +{ + "configs": [ + { + "configKey": "link.default_quantity", + "configValue": "80", + "configType": "INTEGER", + "description": "链接生成默认奖励点数" + }, + { + "configKey": "link.refresh_interval", + "configValue": "600", + "configType": "INTEGER", + "description": "链接刷新间隔(秒)" + } + ] +} +``` + +响应示例: +```json +{ + "success": true, + "message": "批量更新成功", + "updatedCount": 2 +} +``` + +### 根据键快速更新配置值 +```http +PUT /api/admin/config/key/link.default_quantity +Content-Type: application/json + +"100" +``` + +响应示例: +```json +{ + "success": true, + "message": "更新成功" +} +``` + +## 4. 配置类型说明 + +支持的配置类型: +- `STRING`: 字符串类型 +- `INTEGER`: 整数类型 +- `BOOLEAN`: 布尔类型(true/false) +- `JSON`: JSON格式数据 + +## 5. 错误处理 + +### 配置不存在 +```json +{ + "status": 404, + "message": "配置不存在" +} +``` + +### 配置验证失败 +```json +{ + "success": false, + "message": "配置验证失败", + "errors": [ + "配置键 link.default_quantity 的值格式不正确" + ] +} +``` + +### 配置值格式错误 +```json +{ + "success": false, + "message": "配置值格式不正确,期望类型: INTEGER" +} +``` + +## 6. 当前系统配置项 + +根据数据库数据,系统现有以下配置项: + +### 链接相关配置 +- `link.default_quantity`: 链接生成默认奖励点数(默认:50) +- `link.refresh_interval`: 链接刷新间隔秒数(默认:300) +- `link.qr_expire_time`: 二维码过期时间秒数(默认:14400) +- `link.max_times_per_batch`: 每批次最大刷奖励次数(默认:100) +- `link.min_quantity`: 最小奖励点数(默认:10) +- `link.max_quantity`: 最大奖励点数(默认:1000) + +### 脚本相关配置 +- `script.server_url`: 脚本服务器地址 +- `script.qr_path_template`: 二维码图片路径模板 + +### 用户端相关配置 +- `user.qr_expire_seconds`: 用户端二维码有效期秒数(默认:60) +- `user.refresh_wait_seconds`: 用户端刷新等待时间秒数(默认:10) +- `user.link_expire_hours`: 用户端链接有效期小时数(默认:24) +- `user.assets_base_url`: 用户端静态资源基础URL diff --git a/src/main/java/com/gameplatform/server/controller/admin/SystemConfigController.java b/src/main/java/com/gameplatform/server/controller/admin/SystemConfigController.java index 2668ff9..de2bd43 100644 --- a/src/main/java/com/gameplatform/server/controller/admin/SystemConfigController.java +++ b/src/main/java/com/gameplatform/server/controller/admin/SystemConfigController.java @@ -5,6 +5,7 @@ import com.gameplatform.server.model.entity.admin.SystemConfig; import com.gameplatform.server.model.dto.admin.SystemConfigRequest; import com.gameplatform.server.model.dto.admin.SystemConfigResponse; import com.gameplatform.server.model.dto.admin.SystemConfigConverter; +import com.gameplatform.server.model.dto.admin.BatchSystemConfigRequest; import com.gameplatform.server.service.admin.SystemConfigService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,6 +14,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.ArrayList; @RestController @RequestMapping("/api/admin/config") @@ -117,4 +119,93 @@ public class SystemConfigController { public final String qrPathTemplate = systemConfigService.getQrPathTemplate(); }); } + + @GetMapping("/user/config") + @Operation(summary = "获取用户端配置", description = "获取用户端相关配置") + public ResponseEntity getUserConfig() { + return ResponseEntity.ok(new Object() { + public final Integer qrExpireSeconds = systemConfigService.getUserQrExpireSeconds(); + public final Integer refreshWaitSeconds = systemConfigService.getUserRefreshWaitSeconds(); + public final Integer linkExpireHours = systemConfigService.getUserLinkExpireHours(); + public final String assetsBaseUrl = systemConfigService.getUserAssetsBaseUrl(); + }); + } + + @PostMapping("/batch") + @Operation(summary = "批量更新配置", description = "批量更新系统配置,支持根据配置键更新") + public ResponseEntity batchUpdateConfigs(@RequestBody BatchSystemConfigRequest request) { + if (request.getConfigs() == null || request.getConfigs().isEmpty()) { + return ResponseEntity.badRequest().body(new Object() { + public final boolean success = false; + public final String message = "配置列表不能为空"; + }); + } + + List configs = new ArrayList<>(); + List validationErrors = new ArrayList<>(); + + for (BatchSystemConfigRequest.SystemConfigUpdateItem item : request.getConfigs()) { + // 验证配置值 + if (item.getConfigType() != null && item.getConfigValue() != null) { + if (!systemConfigService.validateConfigValue(item.getConfigType(), item.getConfigValue())) { + validationErrors.add("配置键 " + item.getConfigKey() + " 的值格式不正确"); + continue; + } + } + + SystemConfig config = new SystemConfig(); + config.setConfigKey(item.getConfigKey()); + config.setConfigValue(item.getConfigValue()); + config.setConfigType(item.getConfigType()); + config.setDescription(item.getDescription()); + configs.add(config); + } + + if (!validationErrors.isEmpty()) { + return ResponseEntity.badRequest().body(new Object() { + public final boolean success = false; + public final String message = "配置验证失败"; + public final List errors = validationErrors; + }); + } + + boolean success = systemConfigService.updateConfigs(configs); + final boolean finalSuccess = success; + final int finalUpdatedCount = configs.size(); + + return ResponseEntity.ok(new Object() { + public final boolean success = finalSuccess; + public final String message = finalSuccess ? "批量更新成功" : "批量更新失败"; + public final int updatedCount = finalUpdatedCount; + }); + } + + @PutMapping("/key/{configKey}") + @Operation(summary = "根据键更新配置值", description = "根据配置键快速更新配置值") + public ResponseEntity updateConfigByKey( + @PathVariable String configKey, + @RequestBody String configValue) { + + // 先获取现有配置以验证类型 + SystemConfig existingConfig = systemConfigService.getConfigByKey(configKey); + if (existingConfig == null) { + return ResponseEntity.notFound().build(); + } + + // 验证配置值格式 + if (!systemConfigService.validateConfigValue(existingConfig.getConfigType(), configValue)) { + return ResponseEntity.badRequest().body(new Object() { + public final boolean success = false; + public final String message = "配置值格式不正确,期望类型: " + existingConfig.getConfigType(); + }); + } + + boolean success = systemConfigService.updateConfigValueByKey(configKey, configValue); + final boolean finalSuccess = success; + + return ResponseEntity.ok(new Object() { + public final boolean success = finalSuccess; + public final String message = finalSuccess ? "更新成功" : "更新失败"; + }); + } } diff --git a/src/main/java/com/gameplatform/server/model/dto/admin/BatchSystemConfigRequest.java b/src/main/java/com/gameplatform/server/model/dto/admin/BatchSystemConfigRequest.java new file mode 100644 index 0000000..53fe209 --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/admin/BatchSystemConfigRequest.java @@ -0,0 +1,29 @@ +package com.gameplatform.server.model.dto.admin; + +import java.util.List; + +public class BatchSystemConfigRequest { + private List configs; + + public List getConfigs() { return configs; } + public void setConfigs(List configs) { this.configs = configs; } + + public static class SystemConfigUpdateItem { + private String configKey; + private String configValue; + private String configType; + private String description; + + public String getConfigKey() { return configKey; } + public void setConfigKey(String configKey) { this.configKey = configKey; } + + public String getConfigValue() { return configValue; } + public void setConfigValue(String configValue) { this.configValue = configValue; } + + public String getConfigType() { return configType; } + public void setConfigType(String configType) { this.configType = configType; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + } +} diff --git a/src/main/java/com/gameplatform/server/service/admin/SystemConfigService.java b/src/main/java/com/gameplatform/server/service/admin/SystemConfigService.java index e8b7393..d807381 100644 --- a/src/main/java/com/gameplatform/server/service/admin/SystemConfigService.java +++ b/src/main/java/com/gameplatform/server/service/admin/SystemConfigService.java @@ -98,4 +98,86 @@ public class SystemConfigService { public String getQrPathTemplate() { return getConfigValue("script.qr_path_template", "/{machineId}/二维码.png"); } + + // 获取用户端相关配置 + public Integer getUserQrExpireSeconds() { + return getConfigValueAsInt("user.qr_expire_seconds", 60); + } + + public Integer getUserRefreshWaitSeconds() { + return getConfigValueAsInt("user.refresh_wait_seconds", 10); + } + + public Integer getUserLinkExpireHours() { + return getConfigValueAsInt("user.link_expire_hours", 24); + } + + public String getUserAssetsBaseUrl() { + return getConfigValue("user.assets_base_url", "http://36.138.184.60:12345"); + } + + // 批量更新配置 + public boolean updateConfigs(List configs) { + if (configs == null || configs.isEmpty()) { + return false; + } + + int successCount = 0; + for (SystemConfig config : configs) { + if (config.getId() != null) { + if (systemConfigMapper.update(config) > 0) { + successCount++; + } + } else if (config.getConfigKey() != null) { + // 根据key更新 + SystemConfig existing = systemConfigMapper.findByKey(config.getConfigKey()); + if (existing != null) { + config.setId(existing.getId()); + if (systemConfigMapper.update(config) > 0) { + successCount++; + } + } + } + } + + return successCount > 0; + } + + // 根据key批量更新配置值 + public boolean updateConfigValueByKey(String configKey, String configValue) { + SystemConfig config = systemConfigMapper.findByKey(configKey); + if (config == null) { + return false; + } + config.setConfigValue(configValue); + return systemConfigMapper.update(config) > 0; + } + + // 验证配置值的格式是否正确 + public boolean validateConfigValue(String configType, String configValue) { + if (configValue == null || configValue.trim().isEmpty()) { + return false; + } + + try { + switch (configType.toUpperCase()) { + case "INTEGER": + Integer.parseInt(configValue); + return true; + case "BOOLEAN": + String lowerValue = configValue.toLowerCase(); + return "true".equals(lowerValue) || "false".equals(lowerValue); + case "JSON": + // 简单的JSON格式验证 + String trimmed = configValue.trim(); + return (trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]")); + case "STRING": + default: + return true; + } + } catch (NumberFormatException e) { + return false; + } + } } diff --git a/src/main/java/com/gameplatform/server/service/image/ImageSaveService.java b/src/main/java/com/gameplatform/server/service/image/ImageSaveService.java index 3e836d0..5e8d818 100644 --- a/src/main/java/com/gameplatform/server/service/image/ImageSaveService.java +++ b/src/main/java/com/gameplatform/server/service/image/ImageSaveService.java @@ -15,8 +15,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.time.Duration; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; + import java.util.HashMap; import java.util.Map; import java.util.concurrent.Semaphore; @@ -38,8 +37,6 @@ public class ImageSaveService { // 使用信号量限制并发下载数量,防止服务器资源不足 private final Semaphore downloadSemaphore = new Semaphore(3); - // 时间格式化器 - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); public ImageSaveService( ScriptClient scriptClient, @@ -61,14 +58,11 @@ public class ImageSaveService { /** * 下载并保存完成时的所有图片 * @param deviceId 设备ID - * @param taskId 任务ID + * @param codeNo 任务编号 * @return 保存后的本地图片URL映射 */ - public Mono> downloadAndSaveCompletionImages(String deviceId, Long taskId) { - log.info("开始为任务 {} 设备 {} 下载完成图片", taskId, deviceId); - - String timestamp = LocalDateTime.now().format(TIME_FORMATTER); - String taskDir = String.format("task_%d_device_%s_completed_%s", taskId, deviceId, timestamp); + public Mono> downloadAndSaveCompletionImages(String deviceId, String codeNo) { + log.info("开始为任务 {} 设备 {} 下载完成图片", codeNo, deviceId); Map imageTypes = Map.of( "homepage", "首次主页.png", @@ -77,23 +71,20 @@ public class ImageSaveService { "endReward", "结束赏金.png" ); - return downloadAndSaveImages(deviceId, taskDir, imageTypes) + return downloadAndSaveImages(deviceId, codeNo, imageTypes) .doOnSuccess(result -> log.info("任务 {} 设备 {} 完成图片下载保存完成,共保存{}张图片", - taskId, deviceId, result.size())) - .doOnError(error -> log.error("任务 {} 设备 {} 完成图片下载保存失败", taskId, deviceId, error)); + codeNo, deviceId, result.size())) + .doOnError(error -> log.error("任务 {} 设备 {} 完成图片下载保存失败", codeNo, deviceId, error)); } /** * 下载并保存进行中的任务快照图片 * @param deviceId 设备ID - * @param taskId 任务ID + * @param codeNo 任务编号 * @return 保存后的本地图片URL映射 */ - public Mono> downloadAndSaveProgressImages(String deviceId, Long taskId) { - log.debug("开始为任务 {} 设备 {} 下载进度图片", taskId, deviceId); - - String timestamp = LocalDateTime.now().format(TIME_FORMATTER); - String taskDir = String.format("task_%d_device_%s_progress_%s", taskId, deviceId, timestamp); + public Mono> downloadAndSaveProgressImages(String deviceId, String codeNo) { + log.debug("开始为任务 {} 设备 {} 下载进度图片", codeNo, deviceId); // 进行中只保存可能有的图片 Map imageTypes = Map.of( @@ -102,21 +93,21 @@ public class ImageSaveService { "midReward", "中途赏金.png" ); - return downloadAndSaveImages(deviceId, taskDir, imageTypes) + return downloadAndSaveImages(deviceId, codeNo, imageTypes) .doOnSuccess(result -> log.debug("任务 {} 设备 {} 进度图片下载保存完成,共保存{}张图片", - taskId, deviceId, result.size())) - .doOnError(error -> log.warn("任务 {} 设备 {} 进度图片下载保存失败", taskId, deviceId, error)); + codeNo, deviceId, result.size())) + .doOnError(error -> log.warn("任务 {} 设备 {} 进度图片下载保存失败", codeNo, deviceId, error)); } /** * 通用的图片下载保存方法 */ - private Mono> downloadAndSaveImages(String deviceId, String taskDir, Map imageTypes) { + private Mono> downloadAndSaveImages(String deviceId, String codeNo, Map imageTypes) { Map savedImageUrls = new HashMap<>(); return Mono.fromCallable(() -> { - // 创建任务专用目录 - Path taskDirPath = Paths.get(imageSavePath, taskDir); + // 使用codeNo作为目录名 + Path taskDirPath = Paths.get(imageSavePath, codeNo); Files.createDirectories(taskDirPath); return taskDirPath; }) @@ -181,9 +172,9 @@ public class ImageSaveService { return null; } - // 生成本地文件名 + // 直接使用图片类型作为文件名,覆盖同名文件 String extension = getFileExtension(originalName); - String localFileName = imageType + "_" + System.currentTimeMillis() + extension; + String localFileName = imageType + extension; Path localFilePath = taskDirPath.resolve(localFileName); // 保存文件 diff --git a/src/main/java/com/gameplatform/server/service/link/DeviceTaskUpdateService.java b/src/main/java/com/gameplatform/server/service/link/DeviceTaskUpdateService.java index 7ec45c4..52fdc58 100644 --- a/src/main/java/com/gameplatform/server/service/link/DeviceTaskUpdateService.java +++ b/src/main/java/com/gameplatform/server/service/link/DeviceTaskUpdateService.java @@ -85,7 +85,7 @@ public class DeviceTaskUpdateService { for (LinkTask task : tasks) { try { // 异步下载并保存完成图片 - imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getId()) + imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getCodeNo()) .subscribe( savedImages -> { // 保存成功后更新任务 @@ -170,7 +170,7 @@ public class DeviceTaskUpdateService { for (LinkTask task : tasks) { try { // 异步下载并保存完成图片 - imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getId()) + imageSaveService.downloadAndSaveCompletionImages(deviceId, task.getCodeNo()) .subscribe( savedImages -> { // 保存成功后更新任务 @@ -218,36 +218,36 @@ public class DeviceTaskUpdateService { /** * 定期保存进行中任务的图片快照 * @param deviceId 设备ID - * @param taskId 任务ID + * @param codeNo 任务编号 */ - public void saveProgressImagesForTask(String deviceId, Long taskId) { - log.debug("开始为任务 {} 设备 {} 保存进度图片", taskId, deviceId); + public void saveProgressImagesForTask(String deviceId, String codeNo) { + log.debug("开始为任务 {} 设备 {} 保存进度图片", codeNo, deviceId); - imageSaveService.downloadAndSaveProgressImages(deviceId, taskId) + imageSaveService.downloadAndSaveProgressImages(deviceId, codeNo) .subscribe( savedImages -> { if (!savedImages.isEmpty()) { log.info("任务 {} 设备 {} 进度图片保存成功,共保存{}张图片", - taskId, deviceId, savedImages.size()); + codeNo, deviceId, savedImages.size()); // 可选:更新任务记录中的进度图片信息 - updateTaskProgressImages(taskId, savedImages); + updateTaskProgressImagesByCodeNo(codeNo, savedImages); } }, error -> log.warn("任务 {} 设备 {} 进度图片保存失败: {}", - taskId, deviceId, error.getMessage()) + codeNo, deviceId, error.getMessage()) ); } /** * 更新任务的进度图片信息(可选) */ - private void updateTaskProgressImages(Long taskId, Map savedImages) { + private void updateTaskProgressImagesByCodeNo(String codeNo, Map savedImages) { try { // 这里可以选择是否要将进度图片也保存到数据库中 // 目前只记录日志,不修改数据库记录 - log.debug("任务 {} 进度图片已保存到本地: {}", taskId, savedImages.keySet()); + log.debug("任务 {} 进度图片已保存到本地: {}", codeNo, savedImages.keySet()); } catch (Exception e) { - log.warn("更新任务 {} 进度图片记录失败", taskId, e); + log.warn("更新任务 {} 进度图片记录失败", codeNo, e); } } diff --git a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java index 5b11e96..f11d1fa 100644 --- a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java +++ b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java @@ -445,7 +445,7 @@ public class LinkStatusService { log.info("空闲设备检查完成: 总设备数={}, 空闲设备数={}, 空闲设备列表={}", deviceStatus.getTotalDevices(), deviceStatus.getAvailableCount(), deviceStatus.getAvailableDevices()); - // 7. 选择一个空闲设备 + // 7. 选择一个空闲设备 TODO // String selectedDevice = deviceStatus.getAvailableDevices().get(0); // 选择第一个空闲设备 String selectedDevice = "cc2"; log.info("从空闲设备列表中选择设备: {}", selectedDevice); diff --git a/src/main/java/com/gameplatform/server/task/ImageSaveScheduleTask.java b/src/main/java/com/gameplatform/server/task/ImageSaveScheduleTask.java index 72b8206..8a4d3de 100644 --- a/src/main/java/com/gameplatform/server/task/ImageSaveScheduleTask.java +++ b/src/main/java/com/gameplatform/server/task/ImageSaveScheduleTask.java @@ -108,7 +108,7 @@ public class ImageSaveScheduleTask { Thread.sleep(delaySeconds * 1000L); // 保存任务的进度图片 - deviceTaskUpdateService.saveProgressImagesForTask(task.getMachineId(), task.getId()); + deviceTaskUpdateService.saveProgressImagesForTask(task.getMachineId(), task.getCodeNo()); log.debug("任务 {} (设备: {}) 进度图片保存请求已提交", task.getId(), task.getMachineId()); @@ -150,7 +150,7 @@ public class ImageSaveScheduleTask { imageTaskExecutor.execute(() -> { for (LinkTask task : deviceTasks) { try { - deviceTaskUpdateService.saveProgressImagesForTask(deviceId, task.getId()); + deviceTaskUpdateService.saveProgressImagesForTask(deviceId, task.getCodeNo()); Thread.sleep(2000); // 每个任务间延迟2秒 } catch (InterruptedException e) { Thread.currentThread().interrupt();