You've already forked FrameTour-BE
Compare commits
15 Commits
1727619b29
...
master
Author | SHA1 | Date | |
---|---|---|---|
3cb12c13c2 | |||
feac2e8d93 | |||
be375067ce | |||
7dec2e614c | |||
51d0716606 | |||
765998bd97 | |||
5f4f89112b | |||
d68b062951 | |||
99857db006 | |||
e8c645a3c0 | |||
fe8068b3d9 | |||
c689496130 | |||
7e16ad35e7 | |||
b14754ec0a | |||
a888ed3fe2 |
@@ -43,6 +43,12 @@ public class KafkaConfig {
|
||||
@Value("${kafka.producer.buffer-memory:33554432}")
|
||||
private Integer bufferMemory;
|
||||
|
||||
@Value("${kafka.producer.enable-idempotence:true}")
|
||||
private boolean enableIdempotence;
|
||||
|
||||
@Value("${kafka.producer.compression-type:snappy}")
|
||||
private String compressionType;
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> configProps = new HashMap<>();
|
||||
@@ -54,6 +60,8 @@ public class KafkaConfig {
|
||||
configProps.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
|
||||
configProps.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
|
||||
configProps.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
|
||||
configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, enableIdempotence);
|
||||
configProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, compressionType);
|
||||
|
||||
return new DefaultKafkaProducerFactory<>(configProps);
|
||||
}
|
||||
|
@@ -75,8 +75,8 @@ public class AppPrinterController {
|
||||
}
|
||||
@PostMapping("/uploadTo/{scenicId}/formSource")
|
||||
public ApiResponse<?> uploadFromSource(@PathVariable("scenicId") Long scenicId, @RequestBody FromSourceReq req) throws IOException {
|
||||
printerService.addUserPhotoFromSource(JwtTokenUtil.getWorker().getUserId(), scenicId, req);
|
||||
return ApiResponse.success(null);
|
||||
List<Integer> list = printerService.addUserPhotoFromSource(JwtTokenUtil.getWorker().getUserId(), scenicId, req);
|
||||
return ApiResponse.success(list);
|
||||
}
|
||||
|
||||
@PostMapping("/setQuantity/{scenicId}/{id}")
|
||||
|
@@ -72,27 +72,14 @@ public class AppScenicController {
|
||||
public ApiResponse<ScenicConfigResp> getConfig(@PathVariable Long id){
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(id);
|
||||
ScenicConfigResp resp = new ScenicConfigResp();
|
||||
resp.setBookRoutine(scenicConfig.getInteger("book_routine"));
|
||||
resp.setForceFinishTime(scenicConfig.getInteger("force_finish_time"));
|
||||
resp.setTourTime(scenicConfig.getInteger("tour_time"));
|
||||
resp.setSampleStoreDay(scenicConfig.getInteger("sample_store_day"));
|
||||
resp.setFaceStoreDay(scenicConfig.getInteger("face_store_day"));
|
||||
resp.setVideoStoreDay(scenicConfig.getInteger("video_store_day"));
|
||||
resp.setAllFree(scenicConfig.getBoolean("all_free"));
|
||||
resp.setDisableSourceVideo(scenicConfig.getBoolean("disable_source_video"));
|
||||
resp.setDisableSourceImage(scenicConfig.getBoolean("disable_source_image"));
|
||||
resp.setAntiScreenRecordType(scenicConfig.getInteger("anti_screen_record_type"));
|
||||
resp.setVideoSourceStoreDay(scenicConfig.getInteger("video_source_store_day"));
|
||||
resp.setImageSourceStoreDay(scenicConfig.getInteger("image_source_store_day"));
|
||||
resp.setUserSourceExpireDay(scenicConfig.getInteger("user_source_expire_day"));
|
||||
resp.setBrokerDirectRate(scenicConfig.getBigDecimal("broker_direct_rate"));
|
||||
resp.setVideoSourcePackHint(scenicConfig.getString("video_source_pack_hint"));
|
||||
resp.setImageSourcePackHint(scenicConfig.getString("image_source_pack_hint"));
|
||||
resp.setVoucherEnable(scenicConfig.getBoolean("voucher_enable", false));
|
||||
resp.setEnableVoucher(scenicConfig.getBoolean("voucher_enable", false)); // compactible
|
||||
resp.setGroupingEnable(scenicConfig.getBoolean("grouping_enable", false));
|
||||
resp.setShowPhotoWhenWaiting(scenicConfig.getBoolean("show_photo_when_waiting", false));
|
||||
resp.setWatermarkUrl(scenicConfig.getString("watermark_url"));
|
||||
resp.setVideoStoreDay(scenicConfig.getInteger("video_store_day"));
|
||||
resp.setAntiScreenRecordType(scenicConfig.getInteger("anti_screen_record_type"));
|
||||
resp.setGroupingEnable(scenicConfig.getBoolean("grouping_enable", false));
|
||||
resp.setVoucherEnable(scenicConfig.getBoolean("voucher_enable", false));
|
||||
resp.setShowPhotoWhenWaiting(scenicConfig.getBoolean("show_photo_when_waiting", false));
|
||||
resp.setImageSourcePackHint(scenicConfig.getString("image_source_pack_hint"));
|
||||
resp.setVideoSourcePackHint(scenicConfig.getString("video_source_pack_hint"));
|
||||
return ApiResponse.success(resp);
|
||||
}
|
||||
|
||||
|
@@ -63,34 +63,6 @@ public class DeviceV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备V2带配置信息分页列表
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
public ApiResponse<PageResponse<DeviceV2WithConfigDTO>> listDevicesWithConfig(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String no,
|
||||
@RequestParam(required = false) String type,
|
||||
@RequestParam(required = false) Integer isActive,
|
||||
@RequestParam(required = false) Long scenicId) {
|
||||
log.info("分页查询设备带配置信息列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
|
||||
// 参数验证:限制pageSize最大值为100
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
}
|
||||
|
||||
try {
|
||||
PageResponse<DeviceV2WithConfigDTO> response = deviceIntegrationService.listDevicesWithConfig(page, pageSize, name, no, type, isActive, scenicId);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询设备带配置信息列表失败", e);
|
||||
return ApiResponse.fail("分页查询设备带配置信息列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取设备信息
|
||||
*/
|
||||
@@ -105,20 +77,6 @@ public class DeviceV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取设备带配置信息
|
||||
*/
|
||||
@GetMapping("/{id}/with-config")
|
||||
public ApiResponse<DeviceV2WithConfigDTO> getDeviceWithConfig(@PathVariable Long id) {
|
||||
try {
|
||||
DeviceV2WithConfigDTO device = deviceIntegrationService.getDeviceWithConfig(id);
|
||||
return ApiResponse.success(device);
|
||||
} catch (Exception e) {
|
||||
log.error("获取设备配置信息失败, id: {}", id, e);
|
||||
return ApiResponse.fail("获取设备配置信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备信息
|
||||
*/
|
||||
@@ -133,20 +91,6 @@ public class DeviceV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备带配置信息
|
||||
*/
|
||||
@GetMapping("/no/{no}/with-config")
|
||||
public ApiResponse<DeviceV2WithConfigDTO> getDeviceWithConfigByNo(@PathVariable String no) {
|
||||
try {
|
||||
DeviceV2WithConfigDTO device = deviceIntegrationService.getDeviceWithConfigByNo(no);
|
||||
return ApiResponse.success(device);
|
||||
} catch (Exception e) {
|
||||
log.error("根据设备编号获取设备配置信息失败, no: {}", no, e);
|
||||
return ApiResponse.fail("根据设备编号获取设备配置信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备ID获取设备在线状态
|
||||
*/
|
||||
@@ -327,20 +271,6 @@ public class DeviceV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备扁平化配置
|
||||
*/
|
||||
@GetMapping("/{id}/flat-config")
|
||||
public ApiResponse<Map<String, Object>> getDeviceFlatConfig(@PathVariable Long id) {
|
||||
try {
|
||||
Map<String, Object> config = deviceConfigIntegrationService.getDeviceFlatConfig(id);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("获取设备扁平化配置失败, deviceId: {}", id, e);
|
||||
return ApiResponse.fail("获取设备扁平化配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置键获取配置
|
||||
*/
|
||||
@@ -371,21 +301,6 @@ public class DeviceV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备编号获取扁平化配置
|
||||
*/
|
||||
@GetMapping("/no/{no}/flat-config")
|
||||
public ApiResponse<Map<String, Object>> getDeviceFlatConfigByNo(@PathVariable String no) {
|
||||
log.info("根据设备编号获取扁平化配置, deviceNo: {}", no);
|
||||
try {
|
||||
Map<String, Object> config = deviceConfigIntegrationService.getDeviceFlatConfigByNo(no);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("根据设备编号获取扁平化配置失败, deviceNo: {}", no, e);
|
||||
return ApiResponse.fail("根据设备编号获取扁平化配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备配置
|
||||
*/
|
||||
|
@@ -0,0 +1,60 @@
|
||||
package com.ycwl.basic.controller.pc;
|
||||
|
||||
import com.ycwl.basic.integration.message.dto.ChannelsResponse;
|
||||
import com.ycwl.basic.integration.message.dto.MessageListData;
|
||||
import com.ycwl.basic.integration.message.service.MessageIntegrationService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/message/v1")
|
||||
@RequiredArgsConstructor
|
||||
public class MessageController {
|
||||
|
||||
private final MessageIntegrationService messageService;
|
||||
|
||||
@GetMapping("/messages")
|
||||
public ApiResponse<MessageListData> listMessages(
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "20") Integer pageSize,
|
||||
@RequestParam(required = false) String channelId,
|
||||
@RequestParam(required = false) String title,
|
||||
@RequestParam(required = false) String content,
|
||||
@RequestParam(required = false) String sendBiz,
|
||||
@RequestParam(required = false) String sentAtStart,
|
||||
@RequestParam(required = false) String sentAtEnd,
|
||||
@RequestParam(required = false) String createdAtStart,
|
||||
@RequestParam(required = false) String createdAtEnd
|
||||
) {
|
||||
log.info("PC|消息列表查询 page={}, pageSize={}, channelId={}, title={}, sendBiz={}", page, pageSize, channelId, title, sendBiz);
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
}
|
||||
try {
|
||||
MessageListData data = messageService.listMessages(page, pageSize, channelId, title, content, sendBiz,
|
||||
sentAtStart, sentAtEnd, createdAtStart, createdAtEnd);
|
||||
return ApiResponse.success(data);
|
||||
} catch (Exception e) {
|
||||
log.error("PC|消息列表查询失败", e);
|
||||
return ApiResponse.fail("消息列表查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/channels")
|
||||
public ApiResponse<ChannelsResponse> listChannels() {
|
||||
log.info("PC|获取消息通道列表");
|
||||
try {
|
||||
ChannelsResponse data = messageService.listChannels();
|
||||
return ApiResponse.success(data);
|
||||
} catch (Exception e) {
|
||||
log.error("PC|获取消息通道列表失败", e);
|
||||
return ApiResponse.fail("获取消息通道列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,6 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
|
||||
@@ -71,30 +70,6 @@ public class ScenicV2Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 景区V2带配置信息分页列表
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
public ApiResponse<PageResponse<ScenicV2WithConfigDTO>> listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name) {
|
||||
log.info("分页查询景区带配置信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
|
||||
// 参数验证:限制pageSize最大值为100
|
||||
if (pageSize > 100) {
|
||||
pageSize = 100;
|
||||
}
|
||||
|
||||
try {
|
||||
PageResponse<ScenicV2WithConfigDTO> response = scenicIntegrationService.listScenicsWithConfig(page, pageSize, status, name);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询景区带配置信息列表失败", e);
|
||||
return ApiResponse.fail("分页查询景区带配置信息列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单个景区详情
|
||||
*/
|
||||
@@ -192,36 +167,6 @@ public class ScenicV2Controller {
|
||||
|
||||
// ========== 景区配置管理 ==========
|
||||
|
||||
/**
|
||||
* 获取景区及其配置信息
|
||||
*/
|
||||
@GetMapping("/{scenicId}/with-config")
|
||||
public ApiResponse<ScenicV2WithConfigDTO> getScenicWithConfig(@PathVariable Long scenicId) {
|
||||
log.info("获取景区配置信息, scenicId: {}", scenicId);
|
||||
try {
|
||||
ScenicV2WithConfigDTO scenic = scenicIntegrationService.getScenicWithConfig(scenicId);
|
||||
return ApiResponse.success(scenic);
|
||||
} catch (Exception e) {
|
||||
log.error("获取景区配置信息失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("获取景区配置信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区扁平化配置
|
||||
*/
|
||||
@GetMapping("/{scenicId}/flat-config")
|
||||
public ApiResponse<Map<String, Object>> getScenicFlatConfig(@PathVariable Long scenicId) {
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
try {
|
||||
Map<String, Object> config = scenicIntegrationService.getScenicFlatConfig(scenicId);
|
||||
return ApiResponse.success(config);
|
||||
} catch (Exception e) {
|
||||
log.error("获取景区扁平化配置失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("获取景区扁平化配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区配置列表
|
||||
*/
|
||||
@@ -316,20 +261,4 @@ public class ScenicV2Controller {
|
||||
return ApiResponse.fail("批量更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化批量更新景区配置
|
||||
*/
|
||||
@PutMapping("/{scenicId}/flat-config")
|
||||
public ApiResponse<BatchUpdateResponse> batchFlatUpdateConfigs(@PathVariable Long scenicId,
|
||||
@RequestBody Map<String, Object> configs) {
|
||||
log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size());
|
||||
try {
|
||||
BatchUpdateResponse response = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, configs);
|
||||
return ApiResponse.success(response);
|
||||
} catch (Exception e) {
|
||||
log.error("扁平化批量更新景区配置失败, scenicId: {}", scenicId, e);
|
||||
return ApiResponse.fail("扁平化批量更新配置失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@ Currently implemented:
|
||||
- **Device Integration** (`com.ycwl.basic.integration.device`): ZT-Device microservice integration
|
||||
- **Render Worker Integration** (`com.ycwl.basic.integration.render`): ZT-Render-Worker microservice integration
|
||||
- **Questionnaire Integration** (`com.ycwl.basic.integration.questionnaire`): ZT-Questionnaire microservice integration
|
||||
- **Message Integration** (`com.ycwl.basic.integration.message`): ZT-Message Kafka producer integration
|
||||
|
||||
### Integration Pattern
|
||||
|
||||
@@ -34,8 +35,7 @@ service/
|
||||
├── client/ # Feign clients for HTTP calls
|
||||
├── config/ # Service-specific configuration
|
||||
├── dto/ # Data transfer objects
|
||||
├── service/ # Service layer with business logic
|
||||
└── example/ # Usage examples
|
||||
└── service/ # Service layer with business logic
|
||||
```
|
||||
|
||||
## Integration Fallback Mechanism
|
||||
@@ -792,13 +792,6 @@ mvn test -Dtest=DefaultConfigIntegrationServiceTest
|
||||
|
||||
# Run all device integration tests (including default configs)
|
||||
mvn test -Dtest="com.ycwl.basic.integration.device.*Test"
|
||||
|
||||
# Enable example runner in application-dev.yml
|
||||
integration:
|
||||
device:
|
||||
example:
|
||||
default-config:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### Common Configuration Keys
|
||||
@@ -820,8 +813,7 @@ com.ycwl.basic.integration.{service-name}/
|
||||
├── client/
|
||||
├── config/
|
||||
├── dto/
|
||||
├── service/
|
||||
└── example/
|
||||
└── service/
|
||||
```
|
||||
|
||||
### 2. Add Configuration Properties
|
||||
@@ -1168,6 +1160,57 @@ fallbackService.clearAllFallbackCache("zt-render-worker");
|
||||
- **Active (isActive=1)**: Worker is available for tasks
|
||||
- **Inactive (isActive=0)**: Worker is disabled
|
||||
|
||||
## ZT-Message Integration (Kafka Producer)
|
||||
|
||||
### Overview
|
||||
The zt-message microservice accepts messages via Kafka on topic `zt-message`. This integration provides a simple producer service to publish notification messages.
|
||||
|
||||
- Topic: `zt-message`
|
||||
- Key: Use `channelId` for partitioning stability
|
||||
- Value: UTF-8 JSON with fields: `channelId` (required), `title` (required), `content` (required), `target` (required), `extra` (object, optional), `sendReason` (optional), `sendBiz` (optional)
|
||||
|
||||
### Components
|
||||
- `com.ycwl.basic.integration.message.dto.ZtMessage`: DTO for message body
|
||||
- `com.ycwl.basic.integration.message.service.ZtMessageProducerService`: Producer service using Spring Kafka
|
||||
|
||||
### Configuration
|
||||
```yaml
|
||||
kafka:
|
||||
enabled: true # enable Kafka integration
|
||||
bootstrap-servers: 127.0.0.1:9092 # adjust per environment
|
||||
zt-message-topic: zt-message # topic name (default already zt-message)
|
||||
producer:
|
||||
acks: all
|
||||
enable-idempotence: true
|
||||
retries: 5
|
||||
linger-ms: 10
|
||||
batch-size: 32768
|
||||
compression-type: snappy
|
||||
```
|
||||
|
||||
### Usage
|
||||
```java
|
||||
@Autowired
|
||||
private ZtMessageProducerService producer;
|
||||
|
||||
public void sendWelcome() {
|
||||
ZtMessage msg = ZtMessage.of("dummy", "欢迎", "注册成功", "user-001");
|
||||
Map<String, Object> extra = new HashMap<>();
|
||||
extra.put("k", "v");
|
||||
msg.setExtra(extra);
|
||||
msg.setSendReason("REGISTER");
|
||||
msg.setSendBiz("USER");
|
||||
|
||||
producer.send(msg); // key uses channelId, value is JSON
|
||||
}
|
||||
```
|
||||
|
||||
### Notes
|
||||
- Required fields must be non-empty: `channelId`, `title`, `content`, `target`
|
||||
- Keep message body small (< 100 KB)
|
||||
- Use string for 64-bit integers in `extra` to avoid JS precision loss
|
||||
- Service logs the partition/offset upon success, errors on failure
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Running Integration Tests
|
||||
|
@@ -16,8 +16,6 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class ScenicConfigManager extends ConfigManager<ScenicConfigV2DTO> {
|
||||
|
||||
private final Map<String, Object> configMap;
|
||||
|
||||
/**
|
||||
* 从配置列表构造管理器
|
||||
*
|
||||
@@ -25,26 +23,7 @@ public class ScenicConfigManager extends ConfigManager<ScenicConfigV2DTO> {
|
||||
*/
|
||||
public ScenicConfigManager(List<ScenicConfigV2DTO> configList) {
|
||||
super(configList);
|
||||
this.configMap = new HashMap<>();
|
||||
if (configList != null) {
|
||||
for (ScenicConfigV2DTO config : configList) {
|
||||
if (config.getConfigKey() != null && config.getConfigValue() != null) {
|
||||
this.configMap.put(config.getConfigKey(), config.getConfigValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map构造管理器
|
||||
*
|
||||
* @param configMap 配置Map
|
||||
*/
|
||||
public ScenicConfigManager(Map<String, Object> configMap) {
|
||||
super(null); // 使用Map构造时,父类configs为null
|
||||
this.configMap = configMap != null ? new HashMap<>(configMap) : new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfigKey(ScenicConfigV2DTO config) {
|
||||
return config != null ? config.getConfigKey() : null;
|
||||
@@ -54,277 +33,5 @@ public class ScenicConfigManager extends ConfigManager<ScenicConfigV2DTO> {
|
||||
protected Object getConfigValue(ScenicConfigV2DTO config) {
|
||||
return config != null ? config.getConfigValue() : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取长整数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Long值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Long getLong(String key) {
|
||||
return ConfigValueUtil.getLongValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长整数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Long值或默认值
|
||||
*/
|
||||
public Long getLong(String key, Long defaultValue) {
|
||||
Long value = ConfigValueUtil.getLongValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Float值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Float getFloat(String key) {
|
||||
return ConfigValueUtil.getFloatValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Float值或默认值
|
||||
*/
|
||||
public Float getFloat(String key, Float defaultValue) {
|
||||
Float value = ConfigValueUtil.getFloatValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双精度浮点数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Double值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Double getDouble(String key) {
|
||||
return ConfigValueUtil.getDoubleValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双精度浮点数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Double值或默认值
|
||||
*/
|
||||
public Double getDouble(String key, Double defaultValue) {
|
||||
Double value = ConfigValueUtil.getDoubleValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高精度小数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return BigDecimal值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key) {
|
||||
return ConfigValueUtil.getBigDecimalValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高精度小数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return BigDecimal值或默认值
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
|
||||
BigDecimal value = ConfigValueUtil.getBigDecimalValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Boolean值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Boolean getBoolean(String key) {
|
||||
return ConfigValueUtil.getBooleanValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Boolean值或默认值
|
||||
*/
|
||||
public Boolean getBoolean(String key, Boolean defaultValue) {
|
||||
return ConfigValueUtil.getBooleanValue(configMap, key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return true如果键存在,false如果不存在
|
||||
*/
|
||||
public boolean hasKey(String key) {
|
||||
return ConfigValueUtil.hasKey(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在且值不为null
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return true如果键存在且值不为null
|
||||
*/
|
||||
public boolean hasNonNullValue(String key) {
|
||||
return ConfigValueUtil.hasNonNullValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置键
|
||||
*
|
||||
* @return 配置键集合
|
||||
*/
|
||||
public Set<String> getAllKeys() {
|
||||
return new HashSet<>(configMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置项数量
|
||||
*
|
||||
* @return 配置项数量
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return configMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置是否为空
|
||||
*
|
||||
* @return true如果没有配置项
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return configMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置的拷贝
|
||||
*
|
||||
* @return 配置Map的拷贝
|
||||
*/
|
||||
public Map<String, Object> getAllConfigsAsMap() {
|
||||
return new HashMap<>(configMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据键前缀过滤配置
|
||||
*
|
||||
* @param prefix 键前缀
|
||||
* @return 匹配前缀的配置Map
|
||||
*/
|
||||
public Map<String, Object> getConfigsByPrefix(String prefix) {
|
||||
if (prefix == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
return configMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey() != null && entry.getKey().startsWith(prefix))
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
Map.Entry::getValue
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的ScenicConfigManager,包含当前配置的子集
|
||||
*
|
||||
* @param keys 要包含的配置键
|
||||
* @return 包含指定键配置的新管理器
|
||||
*/
|
||||
public ScenicConfigManager subset(Set<String> keys) {
|
||||
Map<String, Object> subsetMap = new HashMap<>();
|
||||
if (keys != null) {
|
||||
for (String key : keys) {
|
||||
if (configMap.containsKey(key)) {
|
||||
subsetMap.put(key, configMap.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ScenicConfigManager(subsetMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将配置转换为扁平化的Map,键名转换为驼峰形式
|
||||
*
|
||||
* @return 扁平化的配置Map,键为驼峰形式
|
||||
*/
|
||||
public Map<String, Object> toFlatConfig() {
|
||||
Map<String, Object> flatConfig = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : configMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (key != null) {
|
||||
String camelCaseKey = toCamelCase(key);
|
||||
flatConfig.put(camelCaseKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return flatConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转换为驼峰形式
|
||||
* 支持下划线、短横线、点号分隔的字符串转换
|
||||
*
|
||||
* @param str 原始字符串
|
||||
* @return 驼峰形式的字符串
|
||||
*/
|
||||
private String toCamelCase(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// 支持下划线、短横线、点号作为分隔符
|
||||
String[] parts = str.split("[_\\-.]");
|
||||
|
||||
if (parts.length <= 1) {
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder camelCase = new StringBuilder();
|
||||
|
||||
// 第一部分保持原样(全小写)
|
||||
camelCase.append(parts[0].toLowerCase());
|
||||
|
||||
// 后续部分首字母大写
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
String part = parts[i];
|
||||
if (!part.isEmpty()) {
|
||||
camelCase.append(Character.toUpperCase(part.charAt(0)));
|
||||
if (part.length() > 1) {
|
||||
camelCase.append(part.substring(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return camelCase.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScenicConfigManager{" +
|
||||
"configCount=" + configMap.size() +
|
||||
", keys=" + configMap.keySet() +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@@ -29,19 +29,7 @@ public interface DeviceConfigV2Client {
|
||||
@GetMapping("/{deviceId}/key/{configKey}")
|
||||
CommonResponse<DeviceConfigV2DTO> getDeviceConfigByKey(@PathVariable("deviceId") Long deviceId,
|
||||
@PathVariable("configKey") String configKey);
|
||||
|
||||
/**
|
||||
* 获取设备扁平化配置
|
||||
*/
|
||||
@GetMapping("/{deviceId}/flat")
|
||||
CommonResponse<Map<String, Object>> getDeviceFlatConfig(@PathVariable("deviceId") Long deviceId);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备扁平化配置
|
||||
*/
|
||||
@GetMapping("/no/{no}/flat")
|
||||
CommonResponse<Map<String, Object>> getDeviceFlatConfigByNo(@PathVariable("no") String no);
|
||||
|
||||
|
||||
/**
|
||||
* 创建设备配置
|
||||
*/
|
||||
|
@@ -20,19 +20,7 @@ public interface DeviceV2Client {
|
||||
*/
|
||||
@GetMapping("/no/{no}")
|
||||
CommonResponse<DeviceV2DTO> getDeviceByNo(@PathVariable("no") String no);
|
||||
|
||||
/**
|
||||
* 获取设备详细信息(含配置)
|
||||
*/
|
||||
@GetMapping("/{id}/with-config")
|
||||
CommonResponse<DeviceV2WithConfigDTO> getDeviceWithConfig(@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备详细信息(含配置)
|
||||
*/
|
||||
@GetMapping("/no/{no}/with-config")
|
||||
CommonResponse<DeviceV2WithConfigDTO> getDeviceByNoWithConfig(@PathVariable("no") String no);
|
||||
|
||||
|
||||
/**
|
||||
* 创建设备
|
||||
*/
|
||||
@@ -64,20 +52,7 @@ public interface DeviceV2Client {
|
||||
@RequestParam(value = "type", required = false) String type,
|
||||
@RequestParam(value = "isActive", required = false) Integer isActive,
|
||||
@RequestParam(value = "scenicId", required = false) Long scenicId);
|
||||
|
||||
/**
|
||||
* 分页获取设备列表(含配置)
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
CommonResponse<PageResponse<DeviceV2WithConfigDTO>> listDevicesWithConfig(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(value = "name", required = false) String name,
|
||||
@RequestParam(value = "no", required = false) String no,
|
||||
@RequestParam(value = "type", required = false) String type,
|
||||
@RequestParam(value = "isActive", required = false) Integer isActive,
|
||||
@RequestParam(value = "scenicId", required = false) Long scenicId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据配置条件筛选设备
|
||||
*/
|
||||
|
@@ -1,275 +0,0 @@
|
||||
package com.ycwl.basic.integration.device.example;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.device.dto.defaults.*;
|
||||
import com.ycwl.basic.integration.device.service.DeviceDefaultConfigIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 默认配置集成服务使用示例
|
||||
*
|
||||
* 通过在 application.yml 中设置 integration.device.example.default-config.enabled=true 来启用
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "integration.device.example.default-config.enabled", havingValue = "true")
|
||||
public class DefaultConfigIntegrationExample implements CommandLineRunner {
|
||||
|
||||
private final DeviceDefaultConfigIntegrationService defaultConfigService;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("=== 默认配置集成服务使用示例 ===");
|
||||
|
||||
try {
|
||||
// 1. 基础查询操作示例(支持自动 Fallback)
|
||||
basicQueryExamples();
|
||||
|
||||
// 2. 配置管理操作示例(直接操作)
|
||||
configManagementExamples();
|
||||
|
||||
// 3. 批量操作示例
|
||||
batchOperationExamples();
|
||||
|
||||
// 4. 高级使用模式示例
|
||||
advancedUsageExamples();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("默认配置集成示例执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础查询操作示例(支持自动 Fallback)
|
||||
*/
|
||||
private void basicQueryExamples() {
|
||||
log.info("--- 基础查询操作示例(支持自动 Fallback)---");
|
||||
|
||||
try {
|
||||
// 获取默认配置列表(自动缓存,服务不可用时返回缓存数据)
|
||||
PageResponse<DefaultConfigResponse> configList = defaultConfigService.listDefaultConfigs(1, 10);
|
||||
log.info("默认配置列表: 总数={}, 当前页配置数={}",
|
||||
configList.getTotal(), configList.getList().size());
|
||||
|
||||
// 显示配置详情
|
||||
for (DefaultConfigResponse config : configList.getList()) {
|
||||
log.info("配置详情: key={}, value={}, type={}, description={}",
|
||||
config.getConfigKey(), config.getConfigValue(),
|
||||
config.getConfigType(), config.getDescription());
|
||||
|
||||
// 获取单个配置详情(自动缓存,服务不可用时返回缓存数据)
|
||||
DefaultConfigResponse detailConfig = defaultConfigService.getDefaultConfig(config.getConfigKey());
|
||||
if (detailConfig != null) {
|
||||
log.info("配置详情获取成功: usageCount={}, isActive={}",
|
||||
detailConfig.getUsageCount(), detailConfig.getIsActive());
|
||||
}
|
||||
break; // 只展示第一个
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("基础查询操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置管理操作示例(直接操作)
|
||||
*/
|
||||
private void configManagementExamples() {
|
||||
log.info("--- 配置管理操作示例(直接操作)---");
|
||||
|
||||
String testConfigKey = "example_test_config";
|
||||
|
||||
try {
|
||||
// 1. 创建默认配置(直接操作,失败时立即报错)
|
||||
DefaultConfigRequest createRequest = new DefaultConfigRequest();
|
||||
createRequest.setConfigKey(testConfigKey);
|
||||
createRequest.setConfigValue("1920x1080");
|
||||
createRequest.setConfigType("string");
|
||||
createRequest.setDescription("示例测试配置 - 默认分辨率");
|
||||
|
||||
boolean createResult = defaultConfigService.createDefaultConfig(createRequest);
|
||||
log.info("创建默认配置结果: {}", createResult ? "成功" : "失败");
|
||||
|
||||
// 2. 更新默认配置(直接操作,可能返回冲突信息)
|
||||
Map<String, Object> updates = new HashMap<>();
|
||||
updates.put("configValue", "3840x2160");
|
||||
updates.put("description", "更新后的默认分辨率 - 4K");
|
||||
|
||||
DefaultConfigConflict conflict = defaultConfigService.updateDefaultConfig(testConfigKey, updates);
|
||||
if (conflict != null) {
|
||||
log.warn("更新配置存在冲突: configKey={}, conflictType={}, deviceCount={}",
|
||||
conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount());
|
||||
} else {
|
||||
log.info("配置更新成功,无冲突");
|
||||
}
|
||||
|
||||
// 3. 验证更新结果
|
||||
DefaultConfigResponse updatedConfig = defaultConfigService.getDefaultConfig(testConfigKey);
|
||||
if (updatedConfig != null) {
|
||||
log.info("更新后配置值: {}", updatedConfig.getConfigValue());
|
||||
}
|
||||
|
||||
// 4. 删除测试配置(直接操作)
|
||||
boolean deleteResult = defaultConfigService.deleteDefaultConfig(testConfigKey);
|
||||
log.info("删除默认配置结果: {}", deleteResult ? "成功" : "失败");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("配置管理操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作示例
|
||||
*/
|
||||
private void batchOperationExamples() {
|
||||
log.info("--- 批量操作示例 ---");
|
||||
|
||||
try {
|
||||
// 1. 使用构建器模式创建批量配置
|
||||
BatchDefaultConfigRequest batchRequest = defaultConfigService.createBatchConfigBuilder()
|
||||
.addVideoConfig("1920x1080", 30, "H264") // 添加视频配置组
|
||||
.addNetworkConfig("192.168.1.100", 554, "RTSP") // 添加网络配置组
|
||||
.addConfig("recording_enabled", "true", "bool", "是否启用录制")
|
||||
.addConfig("storage_path", "/data/recordings", "string", "录制存储路径")
|
||||
.addConfig("max_file_size", "1024", "int", "最大文件大小(MB)")
|
||||
.build();
|
||||
|
||||
log.info("准备批量创建 {} 个默认配置", batchRequest.getConfigs().size());
|
||||
|
||||
// 2. 执行批量更新(直接操作)
|
||||
BatchDefaultConfigResponse batchResult = defaultConfigService.batchUpdateDefaultConfigs(batchRequest);
|
||||
|
||||
// 3. 处理批量结果
|
||||
log.info("批量操作结果: 成功={}, 失败={}", batchResult.getSuccess(), batchResult.getFailed());
|
||||
|
||||
if (batchResult.getConflicts() != null && !batchResult.getConflicts().isEmpty()) {
|
||||
log.warn("发现 {} 个配置冲突:", batchResult.getConflicts().size());
|
||||
for (DefaultConfigConflict conflict : batchResult.getConflicts()) {
|
||||
log.warn("冲突配置: key={}, type={}, deviceCount={}",
|
||||
conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount());
|
||||
}
|
||||
}
|
||||
|
||||
if (batchResult.getProcessedItems() != null) {
|
||||
log.info("处理详情:");
|
||||
batchResult.getProcessedItems().forEach(item ->
|
||||
log.info(" 配置 {}: status={}, action={}, finalType={}",
|
||||
item.getConfigKey(), item.getStatus(), item.getAction(), item.getFinalType())
|
||||
);
|
||||
}
|
||||
|
||||
if (batchResult.getErrors() != null && !batchResult.getErrors().isEmpty()) {
|
||||
log.error("批量操作错误:");
|
||||
batchResult.getErrors().forEach(error -> log.error(" {}", error));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 高级使用模式示例
|
||||
*/
|
||||
private void advancedUsageExamples() {
|
||||
log.info("--- 高级使用模式示例 ---");
|
||||
|
||||
try {
|
||||
// 1. 设备类型特定的默认配置模式
|
||||
createDeviceTypeSpecificConfigs();
|
||||
|
||||
// 2. 配置验证和完整性检查模式
|
||||
validateConfigCompleteness();
|
||||
|
||||
// 3. 配置迁移和批量更新模式
|
||||
configMigrationPattern();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("高级使用模式示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备类型特定的默认配置
|
||||
*/
|
||||
private void createDeviceTypeSpecificConfigs() {
|
||||
log.info("创建设备类型特定的默认配置...");
|
||||
|
||||
// IPC摄像头默认配置
|
||||
BatchDefaultConfigRequest ipcDefaults = defaultConfigService.createBatchConfigBuilder()
|
||||
.addVideoConfig("1920x1080", 25, "H264")
|
||||
.addConfig("night_vision", "true", "bool", "夜视功能")
|
||||
.addConfig("motion_detection", "true", "bool", "移动检测")
|
||||
.addConfig("stream_profile", "main", "string", "码流类型")
|
||||
.build();
|
||||
|
||||
// NVR设备默认配置
|
||||
BatchDefaultConfigRequest nvrDefaults = defaultConfigService.createBatchConfigBuilder()
|
||||
.addConfig("max_channels", "16", "int", "最大通道数")
|
||||
.addConfig("storage_mode", "continuous", "string", "存储模式")
|
||||
.addConfig("backup_enabled", "true", "bool", "备份启用")
|
||||
.build();
|
||||
|
||||
log.info("IPC默认配置项数: {}, NVR默认配置项数: {}",
|
||||
ipcDefaults.getConfigs().size(), nvrDefaults.getConfigs().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置验证和完整性检查
|
||||
*/
|
||||
private void validateConfigCompleteness() {
|
||||
log.info("验证配置完整性...");
|
||||
|
||||
// 获取所有默认配置
|
||||
PageResponse<DefaultConfigResponse> allConfigs = defaultConfigService.listDefaultConfigs(1, 100);
|
||||
|
||||
// 检查必需的基础配置是否存在
|
||||
String[] requiredConfigs = {"resolution", "frameRate", "codec", "protocol"};
|
||||
for (String requiredConfig : requiredConfigs) {
|
||||
boolean exists = allConfigs.getList().stream()
|
||||
.anyMatch(config -> requiredConfig.equals(config.getConfigKey()));
|
||||
log.info("必需配置 {} 存在: {}", requiredConfig, exists ? "✓" : "✗");
|
||||
}
|
||||
|
||||
// 统计配置类型分布
|
||||
Map<String, Long> typeDistribution = new HashMap<>();
|
||||
allConfigs.getList().forEach(config ->
|
||||
typeDistribution.merge(config.getConfigType(), 1L, Long::sum)
|
||||
);
|
||||
|
||||
log.info("配置类型分布: {}", typeDistribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置迁移和批量更新模式
|
||||
*/
|
||||
private void configMigrationPattern() {
|
||||
log.info("配置迁移模式示例...");
|
||||
|
||||
try {
|
||||
// 1. 获取需要升级的配置
|
||||
PageResponse<DefaultConfigResponse> oldConfigs = defaultConfigService.listDefaultConfigs(1, 50);
|
||||
|
||||
// 2. 创建升级配置批次
|
||||
BatchDefaultConfigRequest upgradeRequest = defaultConfigService.createBatchConfigBuilder()
|
||||
.addConfig("api_version", "v2", "string", "API版本")
|
||||
.addConfig("security_mode", "enhanced", "string", "安全模式")
|
||||
.build();
|
||||
|
||||
// 3. 执行批量升级
|
||||
BatchDefaultConfigResponse upgradeResult = defaultConfigService.batchUpdateDefaultConfigs(upgradeRequest);
|
||||
log.info("配置升级结果: 成功={}, 失败={}", upgradeResult.getSuccess(), upgradeResult.getFailed());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("配置迁移示例执行失败(这是正常的,因为是示例代码)", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,190 +0,0 @@
|
||||
package com.ycwl.basic.integration.device.example;
|
||||
|
||||
import com.ycwl.basic.integration.device.dto.device.*;
|
||||
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备配置筛选功能使用示例
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceFilterExample {
|
||||
|
||||
private final DeviceIntegrationService deviceIntegrationService;
|
||||
|
||||
/**
|
||||
* 示例1: 查找高质量户外IPC设备
|
||||
* 条件: 分辨率1920x1080,帧率>=30,位置包含"outdoor"
|
||||
*/
|
||||
public void findHighQualityOutdoorDevices() {
|
||||
log.info("=== 查找高质量户外IPC设备 ===");
|
||||
|
||||
List<ConfigFilter> configFilters = Arrays.asList(
|
||||
new ConfigFilter("resolution", "1920x1080", "eq"),
|
||||
new ConfigFilter("framerate", 30, "gte"),
|
||||
new ConfigFilter("location", "outdoor", "like")
|
||||
);
|
||||
|
||||
Map<String, Object> deviceFilters = new HashMap<>();
|
||||
deviceFilters.put("type", "IPC");
|
||||
deviceFilters.put("isActive", 1);
|
||||
|
||||
FilterDevicesByConfigsRequest request = new FilterDevicesByConfigsRequest();
|
||||
request.setPage(1);
|
||||
request.setPageSize(20);
|
||||
request.setConfigFilters(configFilters);
|
||||
request.setFilterLogic("AND");
|
||||
request.setDeviceFilters(deviceFilters);
|
||||
|
||||
try {
|
||||
FilterDevicesByConfigsResponse response = deviceIntegrationService.filterDevicesByConfigs(request);
|
||||
log.info("找到 {} 个高质量户外IPC设备", response.getTotal());
|
||||
|
||||
response.getList().forEach(device -> {
|
||||
log.info("设备: {} ({}), 配置: {}", device.getName(), device.getNo(), device.getConfig());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("查找高质量户外IPC设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2: 查找缺少关键配置的设备
|
||||
* 条件: 缺少备份服务器或夜视功能配置
|
||||
*/
|
||||
public void findDevicesWithMissingConfigs() {
|
||||
log.info("=== 查找缺少关键配置的设备 ===");
|
||||
|
||||
List<ConfigFilter> configFilters = Arrays.asList(
|
||||
new ConfigFilter("backup_server", null, "is_null"),
|
||||
new ConfigFilter("night_vision", null, "is_null")
|
||||
);
|
||||
|
||||
FilterDevicesByConfigsRequest request = new FilterDevicesByConfigsRequest();
|
||||
request.setConfigFilters(configFilters);
|
||||
request.setFilterLogic("OR"); // 缺少任一配置即返回
|
||||
request.setPageSize(50);
|
||||
|
||||
try {
|
||||
FilterDevicesByConfigsResponse response = deviceIntegrationService.filterDevicesByConfigs(request);
|
||||
log.info("找到 {} 个缺少关键配置的设备", response.getTotal());
|
||||
|
||||
response.getList().forEach(device -> {
|
||||
Map<String, Object> config = device.getConfig();
|
||||
boolean missingBackup = !config.containsKey("backup_server") || config.get("backup_server") == null;
|
||||
boolean missingNightVision = !config.containsKey("night_vision") || config.get("night_vision") == null;
|
||||
|
||||
log.info("设备: {} ({}), 缺少配置: {}{}",
|
||||
device.getName(), device.getNo(),
|
||||
missingBackup ? "备份服务器 " : "",
|
||||
missingNightVision ? "夜视功能 " : "");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("查找缺少关键配置的设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3: 根据多个位置查找设备
|
||||
* 条件: 位置在指定列表中
|
||||
*/
|
||||
public void findDevicesByMultipleLocations() {
|
||||
log.info("=== 根据多个位置查找设备 ===");
|
||||
|
||||
List<String> locations = Arrays.asList("outdoor", "indoor", "parking");
|
||||
List<ConfigFilter> configFilters = Arrays.asList(
|
||||
new ConfigFilter("location", locations, "in")
|
||||
);
|
||||
|
||||
FilterDevicesByConfigsRequest request = new FilterDevicesByConfigsRequest();
|
||||
request.setConfigFilters(configFilters);
|
||||
request.setPageSize(100);
|
||||
|
||||
try {
|
||||
FilterDevicesByConfigsResponse response = deviceIntegrationService.filterDevicesByConfigs(request);
|
||||
log.info("找到 {} 个设备在指定位置", response.getTotal());
|
||||
|
||||
response.getList().forEach(device -> {
|
||||
Object location = device.getConfig().get("location");
|
||||
log.info("设备: {} ({}), 位置: {}", device.getName(), device.getNo(), location);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("根据多个位置查找设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4: 使用便捷方法查找设备
|
||||
*/
|
||||
public void useConvenienceMethods() {
|
||||
log.info("=== 使用便捷方法查找设备 ===");
|
||||
|
||||
try {
|
||||
// 查找缺少配置的设备
|
||||
FilterDevicesByConfigsResponse response4 = deviceIntegrationService
|
||||
.findDevicesWithMissingConfig("firmware_version", 1, 10);
|
||||
log.info("缺少固件版本配置的设备数: {}", response4.getTotal());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("使用便捷方法查找设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5: 性能监控查询
|
||||
* 条件: CPU使用率>80% 或 内存使用率>85%
|
||||
*/
|
||||
public void findHighLoadDevices() {
|
||||
log.info("=== 查找高负载设备 ===");
|
||||
|
||||
List<ConfigFilter> configFilters = Arrays.asList(
|
||||
new ConfigFilter("cpu_usage", 80, "gt"),
|
||||
new ConfigFilter("memory_usage", 85, "gt")
|
||||
);
|
||||
|
||||
FilterDevicesByConfigsRequest request = new FilterDevicesByConfigsRequest();
|
||||
request.setConfigFilters(configFilters);
|
||||
request.setFilterLogic("OR");
|
||||
request.setPageSize(50);
|
||||
|
||||
try {
|
||||
FilterDevicesByConfigsResponse response = deviceIntegrationService.filterDevicesByConfigs(request);
|
||||
log.info("找到 {} 个高负载设备", response.getTotal());
|
||||
|
||||
response.getList().forEach(device -> {
|
||||
Map<String, Object> config = device.getConfig();
|
||||
Object cpuUsage = config.get("cpu_usage");
|
||||
Object memoryUsage = config.get("memory_usage");
|
||||
|
||||
log.info("高负载设备: {} ({}), CPU: {}%, 内存: {}%",
|
||||
device.getName(), device.getNo(), cpuUsage, memoryUsage);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("查找高负载设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
log.info("开始运行设备配置筛选示例...");
|
||||
|
||||
findHighQualityOutdoorDevices();
|
||||
findDevicesWithMissingConfigs();
|
||||
findDevicesByMultipleLocations();
|
||||
useConvenienceMethods();
|
||||
findHighLoadDevices();
|
||||
|
||||
log.info("所有示例运行完成");
|
||||
}
|
||||
}
|
@@ -1,185 +0,0 @@
|
||||
package com.ycwl.basic.integration.device.example;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.device.dto.device.*;
|
||||
import com.ycwl.basic.integration.device.dto.config.*;
|
||||
import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Device Integration 使用示例
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceIntegrationExample {
|
||||
|
||||
private final DeviceIntegrationService deviceService;
|
||||
private final DeviceConfigIntegrationService deviceConfigService;
|
||||
|
||||
/**
|
||||
* 基本设备操作
|
||||
*/
|
||||
public void basicDeviceOperations() {
|
||||
log.info("=== 基本设备操作 ===");
|
||||
|
||||
// 创建IPC摄像头设备(默认排序)
|
||||
DeviceV2DTO ipcDevice = deviceService.createIpcDevice(
|
||||
"前门摄像头", "CAM001", 1001L);
|
||||
log.info("创建IPC设备: {}, 排序值: {}", ipcDevice.getName(), ipcDevice.getSort());
|
||||
|
||||
// 根据ID获取设备信息
|
||||
DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId());
|
||||
log.info("获取设备信息: {}, 排序值: {}", device.getName(), device.getSort());
|
||||
|
||||
// 根据设备编号获取设备信息
|
||||
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001");
|
||||
log.info("根据编号获取设备: {}", deviceByNo.getName());
|
||||
|
||||
// 获取设备详细信息(含配置)
|
||||
DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId());
|
||||
log.info("获取设备配置: {}", deviceWithConfig.getName());
|
||||
|
||||
// 分页查询景区设备列表
|
||||
PageResponse<DeviceV2DTO> deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10);
|
||||
log.info("景区设备列表: 总数={}", deviceList.getTotal());
|
||||
|
||||
// 启用设备
|
||||
deviceService.enableDevice(ipcDevice.getId());
|
||||
log.info("设备已启用");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备排序功能演示
|
||||
*/
|
||||
public void deviceSortingOperations() {
|
||||
log.info("=== 设备排序功能演示 ===");
|
||||
|
||||
Long scenicId = 1001L;
|
||||
|
||||
// 创建带排序的设备
|
||||
DeviceV2DTO camera1 = deviceService.createIpcDeviceWithSort(
|
||||
"大门摄像头", "CAM_GATE", scenicId, 10);
|
||||
log.info("创建摄像头1: {}, 排序: {}", camera1.getName(), camera1.getSort());
|
||||
|
||||
DeviceV2DTO camera2 = deviceService.createIpcDeviceWithSort(
|
||||
"后门摄像头", "CAM_BACK", scenicId, 20);
|
||||
log.info("创建摄像头2: {}, 排序: {}", camera2.getName(), camera2.getSort());
|
||||
|
||||
DeviceV2DTO sensor1 = deviceService.createCustomDeviceWithSort(
|
||||
"温度传感器", "TEMP_01", scenicId, 5);
|
||||
log.info("创建传感器: {}, 排序: {}", sensor1.getName(), sensor1.getSort());
|
||||
|
||||
// 更新设备排序
|
||||
deviceService.updateDeviceSort(camera1.getId(), 1);
|
||||
log.info("更新摄像头1排序为1(置顶)");
|
||||
|
||||
// 获取排序后的设备列表
|
||||
PageResponse<DeviceV2DTO> sortedList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId);
|
||||
log.info("排序后的设备列表:");
|
||||
for (DeviceV2DTO device : sortedList.getList()) {
|
||||
log.info(" - {}: 排序={}, 类型={}", device.getName(), device.getSort(), device.getType());
|
||||
}
|
||||
|
||||
// 批量调整排序演示
|
||||
log.info("--- 批量调整排序演示 ---");
|
||||
deviceService.updateDeviceSort(sensor1.getId(), 15); // 传感器排到中间
|
||||
deviceService.updateDeviceSort(camera2.getId(), 30); // 后门摄像头排到最后
|
||||
log.info("批量排序调整完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备配置管理
|
||||
*/
|
||||
public void deviceConfigurationOperations() {
|
||||
log.info("=== 设备配置管理 ===");
|
||||
|
||||
Long deviceId = 1L;
|
||||
|
||||
// 获取设备所有配置
|
||||
List<DeviceConfigV2DTO> configs = deviceConfigService.getDeviceConfigs(deviceId);
|
||||
log.info("设备配置数量: {}", configs.size());
|
||||
|
||||
// 获取扁平化配置
|
||||
Map<String, Object> flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId);
|
||||
log.info("扁平化配置项数: {}", flatConfig.size());
|
||||
|
||||
// 使用批量配置API
|
||||
BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder()
|
||||
.build();
|
||||
|
||||
BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfig(deviceId, builderRequest);
|
||||
log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序最佳实践演示
|
||||
*/
|
||||
public void sortingBestPractices() {
|
||||
log.info("=== 排序最佳实践演示 ===");
|
||||
|
||||
Long scenicId = 1001L;
|
||||
|
||||
// 推荐使用10的倍数作为排序值
|
||||
DeviceV2DTO device1 = deviceService.createIpcDeviceWithSort(
|
||||
"重要摄像头", "CAM_IMPORTANT", scenicId, 10);
|
||||
|
||||
DeviceV2DTO device2 = deviceService.createIpcDeviceWithSort(
|
||||
"普通摄像头", "CAM_NORMAL", scenicId, 20);
|
||||
|
||||
DeviceV2DTO device3 = deviceService.createIpcDeviceWithSort(
|
||||
"备用摄像头", "CAM_BACKUP", scenicId, 30);
|
||||
|
||||
log.info("使用10的倍数创建设备排序: 10, 20, 30");
|
||||
|
||||
// 在中间插入新设备
|
||||
DeviceV2DTO insertDevice = deviceService.createIpcDeviceWithSort(
|
||||
"中间摄像头", "CAM_MIDDLE", scenicId, 25);
|
||||
log.info("在20和30之间插入设备,排序值: 25");
|
||||
|
||||
// 置顶操作
|
||||
deviceService.updateDeviceSort(device2.getId(), 1);
|
||||
log.info("将普通摄像头置顶(排序值: 1)");
|
||||
|
||||
// 查看最终排序结果
|
||||
PageResponse<DeviceV2DTO> finalList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId);
|
||||
log.info("最终排序结果:");
|
||||
for (DeviceV2DTO device : finalList.getList()) {
|
||||
log.info(" - {}: 排序={}", device.getName(), device.getSort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
try {
|
||||
basicDeviceOperations();
|
||||
deviceSortingOperations();
|
||||
sortingBestPractices();
|
||||
deviceConfigurationOperations();
|
||||
log.info("=== 所有示例执行完成 ===");
|
||||
} catch (Exception e) {
|
||||
log.error("示例执行过程中发生错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行基础示例(简化版)
|
||||
*/
|
||||
public void runBasicExamples() {
|
||||
try {
|
||||
basicDeviceOperations();
|
||||
deviceConfigurationOperations();
|
||||
log.info("=== 基础示例执行完成 ===");
|
||||
} catch (Exception e) {
|
||||
log.error("示例执行过程中发生错误", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
package com.ycwl.basic.integration.device.example;
|
||||
|
||||
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
|
||||
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
|
||||
import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.device.dto.device.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备集成示例(包含降级机制)
|
||||
* 演示设备集成和失败降级策略的使用
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceIntegrationFallbackExample {
|
||||
|
||||
private final DeviceIntegrationService deviceService;
|
||||
private final DeviceConfigIntegrationService configService;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
private static final String SERVICE_NAME = "zt-device";
|
||||
|
||||
/**
|
||||
* 演示设备信息获取的降级机制
|
||||
*/
|
||||
public void deviceInfoFallbackExample() {
|
||||
log.info("=== 设备信息获取降级示例 ===");
|
||||
|
||||
Long deviceId = 1001L;
|
||||
String deviceNo = "CAM001";
|
||||
|
||||
try {
|
||||
// 获取设备信息 - 自动降级
|
||||
DeviceV2DTO device = deviceService.getDevice(deviceId);
|
||||
log.info("获取设备成功: {}", device.getName());
|
||||
|
||||
// 根据设备号获取设备 - 自动降级
|
||||
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo(deviceNo);
|
||||
log.info("根据设备号获取设备成功: {}", deviceByNo.getName());
|
||||
|
||||
// 获取设备配置 - 自动降级
|
||||
DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(deviceId);
|
||||
log.info("获取设备配置成功,配置数量: {}", deviceWithConfig.getConfig().size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("所有降级策略失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示设备操作(无降级机制)
|
||||
*/
|
||||
public void deviceOperationExample() {
|
||||
log.info("=== 设备操作示例 ===");
|
||||
|
||||
Long deviceId = 1001L;
|
||||
|
||||
try {
|
||||
// 设备更新操作 - 直接操作,失败时抛出异常
|
||||
UpdateDeviceRequest updateRequest = new UpdateDeviceRequest();
|
||||
updateRequest.setName("更新后的摄像头");
|
||||
deviceService.updateDevice(deviceId, updateRequest);
|
||||
log.info("设备更新操作完成");
|
||||
|
||||
// 设备排序更新 - 直接操作,失败时抛出异常
|
||||
deviceService.updateDeviceSort(deviceId, 5);
|
||||
log.info("设备排序更新完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("设备操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示降级缓存管理
|
||||
*/
|
||||
public void fallbackCacheManagementExample() {
|
||||
log.info("=== 降级缓存管理示例 ===");
|
||||
|
||||
String deviceCacheKey = "device:1001";
|
||||
String configCacheKey = "device:flat:config:1001";
|
||||
|
||||
// 检查降级缓存状态
|
||||
boolean hasDeviceCache = fallbackService.hasFallbackCache(SERVICE_NAME, deviceCacheKey);
|
||||
boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey);
|
||||
|
||||
log.info("设备降级缓存存在: {}", hasDeviceCache);
|
||||
log.info("配置降级缓存存在: {}", hasConfigCache);
|
||||
|
||||
// 清理特定的降级缓存
|
||||
if (hasDeviceCache) {
|
||||
fallbackService.clearFallbackCache(SERVICE_NAME, deviceCacheKey);
|
||||
log.info("已清理设备降级缓存");
|
||||
}
|
||||
|
||||
// 获取降级缓存统计信息
|
||||
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME);
|
||||
log.info("设备服务降级缓存统计: {}", stats);
|
||||
|
||||
// 批量清理所有设备降级缓存
|
||||
if (stats.getTotalCacheCount() > 10) {
|
||||
fallbackService.clearAllFallbackCache(SERVICE_NAME);
|
||||
log.info("已批量清理所有设备降级缓存");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
log.info("开始运行设备集成示例...");
|
||||
|
||||
deviceInfoFallbackExample();
|
||||
deviceOperationExample();
|
||||
fallbackCacheManagementExample();
|
||||
|
||||
log.info("设备集成示例运行完成");
|
||||
}
|
||||
}
|
@@ -39,31 +39,6 @@ public class DeviceConfigIntegrationService {
|
||||
return handleResponse(response, "根据键获取设备配置失败");
|
||||
}
|
||||
|
||||
public Map<String, Object> getDeviceFlatConfig(Long deviceId) {
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:flat:config:" + deviceId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId);
|
||||
return handleResponse(response, "获取设备扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> getDeviceFlatConfigByNo(String deviceNo) {
|
||||
log.debug("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:flat:config:no:" + deviceNo,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceConfigV2DTO createDeviceConfig(Long deviceId, CreateDeviceConfigRequest request) {
|
||||
log.debug("创建设备配置, deviceId: {}, configKey: {}", deviceId, request.getConfigKey());
|
||||
CommonResponse<DeviceConfigV2DTO> response = deviceConfigV2Client.createDeviceConfig(deviceId, request);
|
||||
|
@@ -48,32 +48,6 @@ public class DeviceIntegrationService {
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) {
|
||||
log.debug("获取设备配置信息, deviceId: {}", deviceId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:config:" + deviceId,
|
||||
() -> {
|
||||
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceWithConfig(deviceId);
|
||||
return handleResponse(response, "获取设备配置信息失败");
|
||||
},
|
||||
DeviceV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) {
|
||||
log.debug("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:config:no:" + deviceNo,
|
||||
() -> {
|
||||
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceByNoWithConfig(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取设备配置信息失败");
|
||||
},
|
||||
DeviceV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2DTO createDevice(CreateDeviceRequest request) {
|
||||
log.debug("创建设备, name: {}, no: {}, type: {}", request.getName(), request.getNo(), request.getType());
|
||||
CommonResponse<DeviceV2DTO> response = deviceV2Client.createDevice(request);
|
||||
@@ -101,15 +75,6 @@ public class DeviceIntegrationService {
|
||||
return handleResponse(response, "分页查询设备列表失败");
|
||||
}
|
||||
|
||||
public PageResponse<DeviceV2WithConfigDTO> listDevicesWithConfig(Integer page, Integer pageSize, String name, String no,
|
||||
String type, Integer isActive, Long scenicId) {
|
||||
log.debug("分页查询设备带配置列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
CommonResponse<PageResponse<DeviceV2WithConfigDTO>> response = deviceV2Client.listDevicesWithConfig(
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
return handleResponse(response, "分页查询设备带配置列表失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建IPC摄像头设备
|
||||
*/
|
||||
|
@@ -11,6 +11,7 @@ public class KafkaIntegrationProperties {
|
||||
|
||||
private boolean enabled = false;
|
||||
private String bootstrapServers = "100.64.0.12:39092";
|
||||
private String ztMessageTopic = "zt-message"; // topic for zt-message microservice
|
||||
private Consumer consumer = new Consumer();
|
||||
private Producer producer = new Producer();
|
||||
|
||||
|
@@ -1,57 +0,0 @@
|
||||
package com.ycwl.basic.integration.kafka.example;
|
||||
|
||||
import com.ycwl.basic.integration.kafka.dto.KafkaMessage;
|
||||
import com.ycwl.basic.integration.kafka.service.KafkaIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Kafka集成使用示例(暂时注释,后续开发时启用)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "kafka.example.enabled", havingValue = "true", matchIfMissing = false)
|
||||
public class KafkaIntegrationExample {
|
||||
|
||||
private final KafkaIntegrationService kafkaService;
|
||||
|
||||
/**
|
||||
* 演示Kafka连接测试
|
||||
*/
|
||||
public void demonstrateConnectionTest() {
|
||||
log.info("=== Kafka Integration Example ===");
|
||||
|
||||
// 测试连接
|
||||
boolean connected = kafkaService.testConnection();
|
||||
log.info("Kafka connection status: {}", connected ? "SUCCESS" : "FAILED");
|
||||
|
||||
// 显示配置信息
|
||||
var properties = kafkaService.getKafkaProperties();
|
||||
log.info("Kafka Bootstrap Servers: {}", properties.getBootstrapServers());
|
||||
log.info("Consumer Group ID: {}", properties.getConsumer().getGroupId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示消息发送(预留示例)
|
||||
*/
|
||||
public void demonstrateMessageSending() {
|
||||
log.info("=== Message Sending Example (Not Implemented) ===");
|
||||
|
||||
// 创建示例消息
|
||||
KafkaMessage<String> message = KafkaMessage.of(
|
||||
"test-topic",
|
||||
"TEST_EVENT",
|
||||
"Hello Kafka from liuying-microservice!"
|
||||
);
|
||||
|
||||
// 发送消息(暂不实现)
|
||||
kafkaService.sendMessage("test-topic", "test-key", message);
|
||||
log.info("Message sending demonstration completed");
|
||||
}
|
||||
|
||||
// TODO: 后续添加消费者示例
|
||||
// public void demonstrateMessageConsuming() { ... }
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.ycwl.basic.integration.message.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.message.dto.ChannelsResponse;
|
||||
import com.ycwl.basic.integration.message.dto.MessageListData;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@FeignClient(name = "zt-message", contextId = "zt-message", path = "")
|
||||
public interface MessageClient {
|
||||
|
||||
@GetMapping("/messages")
|
||||
CommonResponse<MessageListData> listMessages(
|
||||
@RequestParam(name = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(name = "pageSize", defaultValue = "20") Integer pageSize,
|
||||
@RequestParam(name = "channelId", required = false) String channelId,
|
||||
@RequestParam(name = "title", required = false) String title,
|
||||
@RequestParam(name = "content", required = false) String content,
|
||||
@RequestParam(name = "sendBiz", required = false) String sendBiz,
|
||||
@RequestParam(name = "sentAtStart", required = false) String sentAtStart,
|
||||
@RequestParam(name = "sentAtEnd", required = false) String sentAtEnd,
|
||||
@RequestParam(name = "createdAtStart", required = false) String createdAtStart,
|
||||
@RequestParam(name = "createdAtEnd", required = false) String createdAtEnd
|
||||
);
|
||||
|
||||
@GetMapping("/channels")
|
||||
CommonResponse<ChannelsResponse> listChannels();
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.ycwl.basic.integration.message.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ChannelsResponse {
|
||||
private List<String> channels;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.integration.message.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class MessageListData {
|
||||
private List<MessageRecordDTO> list;
|
||||
private String total; // string to avoid JS precision
|
||||
private Integer page;
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package com.ycwl.basic.integration.message.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class MessageRecordDTO {
|
||||
private String id; // string to avoid JS precision
|
||||
private String channelId;
|
||||
private String title;
|
||||
private String content;
|
||||
private String target;
|
||||
private Map<String, Object> extraJson;
|
||||
private String sendReason;
|
||||
private String sendBiz;
|
||||
private String status;
|
||||
private String errorMsg;
|
||||
private Integer attempts;
|
||||
private String sentAt; // RFC3339 or yyyy-MM-dd HH:mm:ss (pass-through)
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.ycwl.basic.integration.message.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ZtMessage {
|
||||
private String channelId; // required
|
||||
private String title; // required
|
||||
private String content; // required
|
||||
private String target; // required
|
||||
private Map<String, Object> extra; // optional
|
||||
private String sendReason; // optional
|
||||
private String sendBiz; // optional
|
||||
|
||||
public static ZtMessage of(String channelId, String title, String content, String target) {
|
||||
return ZtMessage.builder()
|
||||
.channelId(channelId)
|
||||
.title(title)
|
||||
.content(content)
|
||||
.target(target)
|
||||
.extra(new HashMap<>())
|
||||
.build();
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package com.ycwl.basic.integration.message.service;
|
||||
|
||||
import com.ycwl.basic.integration.common.exception.IntegrationException;
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
|
||||
import com.ycwl.basic.integration.message.client.MessageClient;
|
||||
import com.ycwl.basic.integration.message.dto.ChannelsResponse;
|
||||
import com.ycwl.basic.integration.message.dto.MessageListData;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MessageIntegrationService {
|
||||
|
||||
private final MessageClient client;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
private static final String SERVICE_NAME = "zt-message";
|
||||
|
||||
public MessageListData listMessages(Integer page, Integer pageSize,
|
||||
String channelId, String title, String content, String sendBiz,
|
||||
String sentAtStart, String sentAtEnd,
|
||||
String createdAtStart, String createdAtEnd) {
|
||||
log.debug("查询消息列表 page={}, pageSize={}, channelId={}, title={}, sendBiz={}", page, pageSize, channelId, title, sendBiz);
|
||||
CommonResponse<MessageListData> resp = client.listMessages(page, pageSize, channelId, title, content, sendBiz,
|
||||
sentAtStart, sentAtEnd, createdAtStart, createdAtEnd);
|
||||
return handleResponse(resp, "查询消息列表失败");
|
||||
}
|
||||
|
||||
public ChannelsResponse listChannels() {
|
||||
log.debug("查询消息通道列表");
|
||||
// 相对稳定的数据,使用fallback缓存
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"channels",
|
||||
() -> {
|
||||
CommonResponse<ChannelsResponse> resp = client.listChannels();
|
||||
return handleResponse(resp, "查询通道列表失败");
|
||||
},
|
||||
ChannelsResponse.class
|
||||
);
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, SERVICE_NAME);
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.ycwl.basic.integration.message.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.integration.kafka.config.KafkaIntegrationProperties;
|
||||
import com.ycwl.basic.integration.message.dto.ZtMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "kafka.enabled", havingValue = "true")
|
||||
public class ZtMessageProducerService {
|
||||
|
||||
public static final String DEFAULT_TOPIC = "zt-message";
|
||||
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final KafkaIntegrationProperties kafkaProps;
|
||||
|
||||
public void send(ZtMessage msg) {
|
||||
validate(msg);
|
||||
String topic = kafkaProps != null && StringUtils.isNotBlank(kafkaProps.getZtMessageTopic())
|
||||
? kafkaProps.getZtMessageTopic()
|
||||
: DEFAULT_TOPIC;
|
||||
String key = msg.getChannelId();
|
||||
String payload = toJson(msg);
|
||||
|
||||
log.info("[ZT-MESSAGE] producing to topic={}, key={}, title={}", topic, key, msg.getTitle());
|
||||
kafkaTemplate.send(topic, key, payload).whenComplete((metadata, ex) -> {
|
||||
if (ex != null) {
|
||||
log.error("[ZT-MESSAGE] produce failed: {}", ex.getMessage(), ex);
|
||||
} else if (metadata != null) {
|
||||
log.info("[ZT-MESSAGE] produced: partition={}, offset={}", metadata.getRecordMetadata().partition(), metadata.getRecordMetadata().offset());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validate(ZtMessage msg) {
|
||||
if (msg == null) throw new IllegalArgumentException("message is null");
|
||||
if (StringUtils.isBlank(msg.getChannelId())) throw new IllegalArgumentException("channelId is required");
|
||||
if (StringUtils.isBlank(msg.getTitle())) throw new IllegalArgumentException("title is required");
|
||||
if (StringUtils.isBlank(msg.getContent())) throw new IllegalArgumentException("content is required");
|
||||
if (StringUtils.isBlank(msg.getTarget())) throw new IllegalArgumentException("target is required");
|
||||
if (msg.getExtra() != null && !(msg.getExtra() instanceof Map)) {
|
||||
throw new IllegalArgumentException("extra must be a Map");
|
||||
}
|
||||
}
|
||||
|
||||
private String toJson(ZtMessage msg) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(msg);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("failed to serialize message", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,306 +0,0 @@
|
||||
package com.ycwl.basic.integration.questionnaire.example;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.answer.AnswerRequest;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.answer.ResponseDetailResponse;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.answer.SubmitAnswerRequest;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.question.CreateQuestionOptionRequest;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.question.CreateQuestionRequest;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.CreateQuestionnaireRequest;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.questionnaire.QuestionnaireResponse;
|
||||
import com.ycwl.basic.integration.questionnaire.dto.statistics.QuestionnaireStatistics;
|
||||
import com.ycwl.basic.integration.questionnaire.service.QuestionnaireIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "integration.questionnaire.example", name = "enabled", havingValue = "true")
|
||||
public class QuestionnaireIntegrationExample {
|
||||
|
||||
private final QuestionnaireIntegrationService questionnaireService;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void runExamples() {
|
||||
try {
|
||||
log.info("=== 开始问卷集成服务示例 ===");
|
||||
|
||||
// 示例1:创建问卷
|
||||
createQuestionnaireExample();
|
||||
|
||||
// 示例2:查询问卷
|
||||
queryQuestionnaireExample();
|
||||
|
||||
// 示例3:提交答案
|
||||
submitAnswerExample();
|
||||
|
||||
// 示例4:统计查询
|
||||
statisticsExample();
|
||||
|
||||
// 示例5:Fallback 缓存管理
|
||||
fallbackCacheExample();
|
||||
|
||||
log.info("=== 问卷集成服务示例完成 ===");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("问卷集成服务示例执行失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例1:创建问卷
|
||||
*/
|
||||
private void createQuestionnaireExample() {
|
||||
log.info("--- 示例1:创建客户满意度问卷 ---");
|
||||
|
||||
try {
|
||||
CreateQuestionnaireRequest request = new CreateQuestionnaireRequest();
|
||||
request.setName("客户满意度调查");
|
||||
request.setDescription("用于了解客户对我们服务的满意度");
|
||||
request.setIsAnonymous(true);
|
||||
request.setMaxAnswers(1000);
|
||||
|
||||
// 添加单选题
|
||||
CreateQuestionRequest question1 = new CreateQuestionRequest();
|
||||
question1.setTitle("您对我们的服务满意吗?");
|
||||
question1.setType(1); // 单选题
|
||||
question1.setIsRequired(true);
|
||||
question1.setSort(1);
|
||||
|
||||
List<CreateQuestionOptionRequest> options1 = new ArrayList<>();
|
||||
options1.add(new CreateQuestionOptionRequest("非常满意", "5", 1));
|
||||
options1.add(new CreateQuestionOptionRequest("满意", "4", 2));
|
||||
options1.add(new CreateQuestionOptionRequest("一般", "3", 3));
|
||||
options1.add(new CreateQuestionOptionRequest("不满意", "2", 4));
|
||||
options1.add(new CreateQuestionOptionRequest("非常不满意", "1", 5));
|
||||
question1.setOptions(options1);
|
||||
|
||||
// 添加多选题
|
||||
CreateQuestionRequest question2 = new CreateQuestionRequest();
|
||||
question2.setTitle("您感兴趣的服务有哪些?");
|
||||
question2.setType(2); // 多选题
|
||||
question2.setIsRequired(false);
|
||||
question2.setSort(2);
|
||||
|
||||
List<CreateQuestionOptionRequest> options2 = new ArrayList<>();
|
||||
options2.add(new CreateQuestionOptionRequest("技术支持", "tech_support", 1));
|
||||
options2.add(new CreateQuestionOptionRequest("产品培训", "training", 2));
|
||||
options2.add(new CreateQuestionOptionRequest("定制开发", "custom_dev", 3));
|
||||
options2.add(new CreateQuestionOptionRequest("其他", "others", 4));
|
||||
question2.setOptions(options2);
|
||||
|
||||
// 添加文本域题
|
||||
CreateQuestionRequest question3 = new CreateQuestionRequest();
|
||||
question3.setTitle("您还有什么建议吗?");
|
||||
question3.setType(4); // 文本域题
|
||||
question3.setIsRequired(false);
|
||||
question3.setSort(3);
|
||||
question3.setOptions(null); // 文本域题不需要选项
|
||||
|
||||
request.setQuestions(Arrays.asList(question1, question2, question3));
|
||||
|
||||
QuestionnaireResponse response = questionnaireService.createQuestionnaire(request, "admin");
|
||||
log.info("✅ 问卷创建成功,ID: {}, 名称: {}", response.getId(), response.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 创建问卷示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:查询问卷
|
||||
*/
|
||||
private void queryQuestionnaireExample() {
|
||||
log.info("--- 示例2:查询问卷示例 ---");
|
||||
|
||||
try {
|
||||
// 获取问卷列表(支持 fallback)
|
||||
PageResponse<QuestionnaireResponse> listResponse = questionnaireService.getQuestionnaireList(1, 10, null, null, null);
|
||||
log.info("✅ 问卷列表查询成功,总数: {}, 当前页数据: {}",
|
||||
listResponse.getTotal(), listResponse.getList().size());
|
||||
|
||||
if (listResponse.getList() != null && !listResponse.getList().isEmpty()) {
|
||||
Long questionnaireId = listResponse.getList().get(0).getId();
|
||||
|
||||
// 获取问卷详情(支持 fallback)
|
||||
QuestionnaireResponse detailResponse = questionnaireService.getQuestionnaire(questionnaireId);
|
||||
log.info("✅ 问卷详情查询成功,ID: {}, 名称: {}, 问题数: {}",
|
||||
detailResponse.getId(), detailResponse.getName(),
|
||||
detailResponse.getQuestions() != null ? detailResponse.getQuestions().size() : 0);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 查询问卷示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:提交答案
|
||||
*/
|
||||
private void submitAnswerExample() {
|
||||
log.info("--- 示例3:提交问卷答案示例 ---");
|
||||
|
||||
try {
|
||||
SubmitAnswerRequest request = new SubmitAnswerRequest();
|
||||
request.setQuestionnaireId(1001L);
|
||||
request.setUserId("user123");
|
||||
|
||||
List<AnswerRequest> answers = new ArrayList<>();
|
||||
// 单选题答案
|
||||
answers.add(new AnswerRequest(123L, "4")); // 满意
|
||||
// 多选题答案
|
||||
answers.add(new AnswerRequest(124L, "tech_support,training")); // 技术支持和产品培训
|
||||
// 文本域题答案
|
||||
answers.add(new AnswerRequest(125L, "服务很好,希望能增加更多实用功能"));
|
||||
|
||||
request.setAnswers(answers);
|
||||
|
||||
ResponseDetailResponse response = questionnaireService.submitAnswer(request);
|
||||
log.info("✅ 问卷答案提交成功,回答ID: {}, 提交时间: {}",
|
||||
response.getId(), response.getSubmittedAt());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 提交答案示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:统计查询
|
||||
*/
|
||||
private void statisticsExample() {
|
||||
log.info("--- 示例4:问卷统计查询示例 ---");
|
||||
|
||||
try {
|
||||
Long questionnaireId = 1001L;
|
||||
|
||||
// 获取问卷统计(支持 fallback)
|
||||
QuestionnaireStatistics stats = questionnaireService.getStatistics(questionnaireId);
|
||||
log.info("✅ 统计查询成功,总回答数: {}, 完成率: {}%, 平均用时: {}秒",
|
||||
stats.getTotalResponses(),
|
||||
stats.getCompletionRate() != null ? stats.getCompletionRate() * 100 : 0,
|
||||
stats.getAverageTime());
|
||||
|
||||
// 获取回答记录列表(支持 fallback)
|
||||
questionnaireService.getResponseList(1, 10, questionnaireId, null, null, null);
|
||||
log.info("✅ 回答记录列表查询成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 统计查询示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:Fallback 缓存管理
|
||||
*/
|
||||
private void fallbackCacheExample() {
|
||||
log.info("--- 示例5:Fallback 缓存管理示例 ---");
|
||||
|
||||
try {
|
||||
String serviceName = "zt-questionnaire";
|
||||
|
||||
// 检查缓存状态
|
||||
boolean hasQuestionnaireCache = fallbackService.hasFallbackCache(serviceName, "questionnaire:1001");
|
||||
boolean hasListCache = fallbackService.hasFallbackCache(serviceName, "questionnaire:list:1:10:null:null:null");
|
||||
log.info("✅ 缓存状态检查 - 问卷缓存: {}, 列表缓存: {}", hasQuestionnaireCache, hasListCache);
|
||||
|
||||
// 获取缓存统计
|
||||
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(serviceName);
|
||||
log.info("✅ 缓存统计 - 缓存项目数: {}, TTL: {} 天",
|
||||
stats.getTotalCacheCount(), stats.getFallbackTtlDays());
|
||||
|
||||
// 清理特定缓存示例(仅演示,实际使用时谨慎操作)
|
||||
// fallbackService.clearFallbackCache(serviceName, "questionnaire:1001");
|
||||
// log.info("✅ 已清理问卷缓存");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Fallback 缓存管理示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 问卷管理工作流示例
|
||||
*/
|
||||
public void questionnaireWorkflowExample(String userId) {
|
||||
log.info("--- 问卷管理工作流示例 ---");
|
||||
|
||||
try {
|
||||
// 1. 创建问卷
|
||||
CreateQuestionnaireRequest createRequest = createSampleQuestionnaire();
|
||||
QuestionnaireResponse questionnaire = questionnaireService.createQuestionnaire(createRequest, userId);
|
||||
log.info("✅ 步骤1 - 问卷创建成功: {}", questionnaire.getName());
|
||||
|
||||
Long questionnaireId = questionnaire.getId();
|
||||
|
||||
// 2. 发布问卷
|
||||
QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaireId, userId);
|
||||
log.info("✅ 步骤2 - 问卷发布成功,状态: {}", published.getStatusText());
|
||||
|
||||
// 3. 模拟用户提交答案
|
||||
SubmitAnswerRequest answerRequest = createSampleAnswers(questionnaireId);
|
||||
ResponseDetailResponse answerResponse = questionnaireService.submitAnswer(answerRequest);
|
||||
log.info("✅ 步骤3 - 答案提交成功: {}", answerResponse.getId());
|
||||
|
||||
// 4. 查看统计数据
|
||||
QuestionnaireStatistics statistics = questionnaireService.getStatistics(questionnaireId);
|
||||
log.info("✅ 步骤4 - 统计查询成功,回答数: {}", statistics.getTotalResponses());
|
||||
|
||||
// 5. 停止问卷
|
||||
QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaireId, userId);
|
||||
log.info("✅ 步骤5 - 问卷停止成功,状态: {}", stopped.getStatusText());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 问卷管理工作流示例失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private CreateQuestionnaireRequest createSampleQuestionnaire() {
|
||||
CreateQuestionnaireRequest request = new CreateQuestionnaireRequest();
|
||||
request.setName("产品体验调查");
|
||||
request.setDescription("收集用户对产品的使用体验反馈");
|
||||
request.setIsAnonymous(false);
|
||||
request.setMaxAnswers(500);
|
||||
|
||||
// 评分题
|
||||
CreateQuestionRequest ratingQuestion = new CreateQuestionRequest();
|
||||
ratingQuestion.setTitle("请对我们的产品进行评分(1-10分)");
|
||||
ratingQuestion.setType(5); // 评分题
|
||||
ratingQuestion.setIsRequired(true);
|
||||
ratingQuestion.setSort(1);
|
||||
ratingQuestion.setOptions(null); // 评分题不需要选项
|
||||
|
||||
// 填空题
|
||||
CreateQuestionRequest textQuestion = new CreateQuestionRequest();
|
||||
textQuestion.setTitle("请输入您的姓名");
|
||||
textQuestion.setType(3); // 填空题
|
||||
textQuestion.setIsRequired(true);
|
||||
textQuestion.setSort(2);
|
||||
textQuestion.setOptions(null); // 填空题不需要选项
|
||||
|
||||
request.setQuestions(Arrays.asList(ratingQuestion, textQuestion));
|
||||
return request;
|
||||
}
|
||||
|
||||
private SubmitAnswerRequest createSampleAnswers(Long questionnaireId) {
|
||||
SubmitAnswerRequest request = new SubmitAnswerRequest();
|
||||
request.setQuestionnaireId(questionnaireId);
|
||||
request.setUserId("test_user");
|
||||
|
||||
List<AnswerRequest> answers = new ArrayList<>();
|
||||
answers.add(new AnswerRequest(1L, "8")); // 评分题答案
|
||||
answers.add(new AnswerRequest(2L, "张三")); // 填空题答案
|
||||
|
||||
request.setAnswers(answers);
|
||||
return request;
|
||||
}
|
||||
}
|
@@ -17,13 +17,7 @@ public interface RenderWorkerV2Client {
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
CommonResponse<RenderWorkerV2DTO> getWorker(@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 获取工作器含配置信息
|
||||
*/
|
||||
@GetMapping("/{id}/with-config")
|
||||
CommonResponse<RenderWorkerV2WithConfigDTO> getWorkerWithConfig(@PathVariable("id") Long id);
|
||||
|
||||
|
||||
/**
|
||||
* 创建工作器
|
||||
*/
|
||||
@@ -51,26 +45,10 @@ public interface RenderWorkerV2Client {
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer isEnabled,
|
||||
@RequestParam(required = false) String name);
|
||||
|
||||
/**
|
||||
* 分页查询工作器列表(含配置信息)
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
CommonResponse<PageResponse<RenderWorkerV2WithConfigDTO>> listWorkersWithConfig(
|
||||
@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer isEnabled,
|
||||
@RequestParam(required = false) String name);
|
||||
|
||||
|
||||
/**
|
||||
* 根据key获取工作器核心信息
|
||||
*/
|
||||
@GetMapping("/key/{key}")
|
||||
CommonResponse<RenderWorkerV2DTO> getWorkerByKey(@PathVariable("key") String key);
|
||||
|
||||
/**
|
||||
* 根据key获取工作器完整信息(含配置)
|
||||
*/
|
||||
@GetMapping("/key/{key}/with-config")
|
||||
CommonResponse<RenderWorkerV2WithConfigDTO> getWorkerWithConfigByKey(@PathVariable("key") String key);
|
||||
}
|
@@ -70,7 +70,7 @@ public class RenderWorkerConfigIntegrationService {
|
||||
log.debug("获取渲染工作器平铺配置, workerId: {}", workerId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"worker:flat:config:" + workerId,
|
||||
"worker:config:" + workerId,
|
||||
() -> {
|
||||
List<RenderWorkerConfigV2DTO> configs = getWorkerConfigsInternal(workerId);
|
||||
return flattenConfigs(configs);
|
||||
|
@@ -42,22 +42,6 @@ public class RenderWorkerIntegrationService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工作器详细信息(含配置)(带降级)
|
||||
*/
|
||||
public RenderWorkerV2WithConfigDTO getWorkerWithConfig(Long id) {
|
||||
log.debug("获取渲染工作器详细信息, id: {}", id);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"worker:config:" + id,
|
||||
() -> {
|
||||
CommonResponse<RenderWorkerV2WithConfigDTO> response = renderWorkerV2Client.getWorkerWithConfig(id);
|
||||
return handleResponse(response, "获取渲染工作器详细信息失败");
|
||||
},
|
||||
RenderWorkerV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建工作器(直接调用,不降级)
|
||||
*/
|
||||
@@ -96,18 +80,6 @@ public class RenderWorkerIntegrationService {
|
||||
return handleResponse(response, "查询渲染工作器列表失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询工作器列表(含配置信息)(不降级)
|
||||
*/
|
||||
public PageResponse<RenderWorkerV2WithConfigDTO> listWorkersWithConfig(Integer page, Integer pageSize,
|
||||
Integer isEnabled, String name) {
|
||||
log.debug("分页查询渲染工作器列表(含配置), page: {}, pageSize: {}, isEnabled: {}, name: {}",
|
||||
page, pageSize, isEnabled, name);
|
||||
CommonResponse<PageResponse<RenderWorkerV2WithConfigDTO>> response =
|
||||
renderWorkerV2Client.listWorkersWithConfig(page, pageSize, isEnabled, name);
|
||||
return handleResponse(response, "查询渲染工作器列表(含配置)失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key获取工作器核心信息(带降级)
|
||||
*/
|
||||
@@ -124,22 +96,6 @@ public class RenderWorkerIntegrationService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key获取工作器详细信息(含配置)(带降级)
|
||||
*/
|
||||
public RenderWorkerV2WithConfigDTO getWorkerWithConfigByKey(String key) {
|
||||
log.debug("根据key获取渲染工作器详细信息, key: {}", key);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"worker:key:config:" + key,
|
||||
() -> {
|
||||
CommonResponse<RenderWorkerV2WithConfigDTO> response = renderWorkerV2Client.getWorkerWithConfigByKey(key);
|
||||
return handleResponse(response, "根据key获取渲染工作器详细信息失败");
|
||||
},
|
||||
RenderWorkerV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通用响应
|
||||
*/
|
||||
|
@@ -18,9 +18,6 @@ public interface ScenicConfigV2Client {
|
||||
CommonResponse<ScenicConfigV2DTO> getConfigByKey(@PathVariable("scenicId") Long scenicId,
|
||||
@PathVariable("configKey") String configKey);
|
||||
|
||||
@GetMapping("/{scenicId}/keys")
|
||||
CommonResponse<Map<String, Object>> getFlatConfigs(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@PostMapping("/{scenicId}")
|
||||
CommonResponse<ScenicConfigV2DTO> createConfig(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody CreateConfigRequest request);
|
||||
@@ -37,8 +34,4 @@ public interface ScenicConfigV2Client {
|
||||
@PostMapping("/{scenicId}/batch")
|
||||
CommonResponse<BatchUpdateResponse> batchUpdateConfigs(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody BatchConfigRequest request);
|
||||
|
||||
@PostMapping("/{scenicId}/batchFlatUpdate")
|
||||
CommonResponse<BatchUpdateResponse> batchFlatUpdateConfigs(@PathVariable("scenicId") Long scenicId,
|
||||
@RequestBody Map<String, Object> configs);
|
||||
}
|
@@ -5,7 +5,6 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
@@ -19,10 +18,6 @@ public interface ScenicV2Client {
|
||||
@GetMapping("/{scenicId}")
|
||||
CommonResponse<ScenicV2DTO> getScenic(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
@GetMapping("/{scenicId}/with-config")
|
||||
CommonResponse<ScenicV2WithConfigDTO> getScenicWithConfig(@PathVariable("scenicId") Long scenicId);
|
||||
|
||||
|
||||
@PostMapping("/")
|
||||
CommonResponse<ScenicV2DTO> createScenic(@RequestBody CreateScenicRequest request);
|
||||
|
||||
@@ -41,10 +36,4 @@ public interface ScenicV2Client {
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name);
|
||||
|
||||
@GetMapping("/with-config")
|
||||
CommonResponse<PageResponse<ScenicV2WithConfigDTO>> listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) String name);
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
package com.ycwl.basic.integration.scenic.dto.scenic;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ScenicV2WithConfigDTO extends ScenicV2DTO {
|
||||
@JsonProperty("config")
|
||||
private Map<String, Object> config;
|
||||
}
|
@@ -1,184 +0,0 @@
|
||||
package com.ycwl.basic.integration.scenic.example;
|
||||
|
||||
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.*;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.*;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.*;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 景区集成示例(包含降级机制)
|
||||
* 演示景区集成和失败降级策略的使用
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ScenicIntegrationExample {
|
||||
|
||||
private final ScenicIntegrationService scenicIntegrationService;
|
||||
private final ScenicConfigIntegrationService scenicConfigIntegrationService;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
private static final String SERVICE_NAME = "zt-scenic";
|
||||
|
||||
/**
|
||||
* 示例:创建景区并设置配置
|
||||
*/
|
||||
public void createScenicWithConfig() {
|
||||
try {
|
||||
// 1. 创建景区
|
||||
CreateScenicRequest createRequest = new CreateScenicRequest();
|
||||
createRequest.setName("测试景区");
|
||||
createRequest.setMpId(1001);
|
||||
|
||||
var scenic = scenicIntegrationService.createScenic(createRequest);
|
||||
log.info("创建景区成功: {}", scenic.getName());
|
||||
|
||||
// 2. 为景区添加配置
|
||||
CreateConfigRequest configRequest = new CreateConfigRequest();
|
||||
configRequest.setConfigKey("tour_time");
|
||||
configRequest.setConfigValue("120");
|
||||
configRequest.setConfigType("int");
|
||||
configRequest.setDescription("游览时长");
|
||||
|
||||
var config = scenicConfigIntegrationService.createConfig(
|
||||
Long.valueOf(scenic.getId()), configRequest);
|
||||
log.info("创建配置成功: {} = {}", config.getConfigKey(), config.getConfigValue());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建景区和配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:筛选景区
|
||||
*/
|
||||
public void filterScenics() {
|
||||
try {
|
||||
FilterCondition condition = new FilterCondition();
|
||||
condition.setConfigKey("tour_time");
|
||||
condition.setConfigValue("120");
|
||||
condition.setOperator("gte");
|
||||
|
||||
ScenicFilterRequest filterRequest = new ScenicFilterRequest();
|
||||
filterRequest.setFilters(Collections.singletonList(condition));
|
||||
filterRequest.setPage(1);
|
||||
filterRequest.setPageSize(10);
|
||||
|
||||
var result = scenicIntegrationService.filterScenics(filterRequest);
|
||||
log.info("筛选到 {} 个景区", result.getTotal());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("筛选景区失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示基础景区操作的降级机制
|
||||
*/
|
||||
public void basicScenicOperationsExample() {
|
||||
log.info("=== 基础景区操作示例(含降级机制) ===");
|
||||
|
||||
Long scenicId = 2001L;
|
||||
|
||||
try {
|
||||
// 获取景区信息 - 自动降级
|
||||
ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId);
|
||||
log.info("获取景区成功: {}", scenic.getName());
|
||||
|
||||
// 获取景区配置信息 - 自动降级
|
||||
ScenicV2WithConfigDTO scenicWithConfig = scenicIntegrationService.getScenicWithConfig(scenicId);
|
||||
log.info("获取景区配置成功,配置数量: {}", scenicWithConfig.getConfig().size());
|
||||
|
||||
// 获取扁平化配置 - 自动降级
|
||||
Map<String, Object> flatConfig = scenicIntegrationService.getScenicFlatConfig(scenicId);
|
||||
log.info("获取扁平化配置成功,配置项数量: {}", flatConfig.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("景区操作降级失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示景区配置管理的降级机制
|
||||
*/
|
||||
public void scenicConfigManagementFallbackExample() {
|
||||
log.info("=== 景区配置管理示例(含降级机制) ===");
|
||||
|
||||
Long scenicId = 2001L;
|
||||
|
||||
try {
|
||||
// 获取扁平化配置 - 自动降级
|
||||
Map<String, Object> flatConfigs = scenicConfigIntegrationService.getFlatConfigs(scenicId);
|
||||
log.info("获取扁平化配置成功,配置项数量: {}", flatConfigs.size());
|
||||
|
||||
// 批量更新配置 - 直接操作,失败时抛出异常
|
||||
Map<String, Object> updates = new HashMap<>();
|
||||
updates.put("max_visitors", "5000");
|
||||
updates.put("opening_hours", "08:00-18:00");
|
||||
|
||||
BatchUpdateResponse result = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, updates);
|
||||
log.info("批量更新配置完成: 成功 {}, 失败 {}", result.getSuccess(), result.getFailed());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("景区配置管理操作失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示降级缓存管理
|
||||
*/
|
||||
public void fallbackCacheManagementExample() {
|
||||
log.info("=== 景区降级缓存管理示例 ===");
|
||||
|
||||
String scenicCacheKey = "scenic:2001";
|
||||
String configCacheKey = "scenic:flat:configs:2001";
|
||||
|
||||
// 检查降级缓存状态
|
||||
boolean hasScenicCache = fallbackService.hasFallbackCache(SERVICE_NAME, scenicCacheKey);
|
||||
boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey);
|
||||
|
||||
log.info("景区降级缓存存在: {}", hasScenicCache);
|
||||
log.info("配置降级缓存存在: {}", hasConfigCache);
|
||||
|
||||
// 获取降级缓存统计信息
|
||||
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME);
|
||||
log.info("景区服务降级缓存统计: 缓存数量={}, TTL={}天",
|
||||
stats.getTotalCacheCount(), stats.getFallbackTtlDays());
|
||||
|
||||
// 清理特定的降级缓存
|
||||
if (hasScenicCache) {
|
||||
fallbackService.clearFallbackCache(SERVICE_NAME, scenicCacheKey);
|
||||
log.info("已清理景区降级缓存");
|
||||
}
|
||||
|
||||
// 如果缓存过多,批量清理
|
||||
if (stats.getTotalCacheCount() > 50) {
|
||||
fallbackService.clearAllFallbackCache(SERVICE_NAME);
|
||||
log.info("已批量清理所有景区降级缓存");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
log.info("开始运行景区集成示例(包含降级机制)...");
|
||||
|
||||
createScenicWithConfig();
|
||||
filterScenics();
|
||||
basicScenicOperationsExample();
|
||||
scenicConfigManagementFallbackExample();
|
||||
fallbackCacheManagementExample();
|
||||
|
||||
log.info("景区集成示例运行完成");
|
||||
}
|
||||
}
|
@@ -48,19 +48,6 @@ public class ScenicConfigIntegrationService {
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> getFlatConfigs(Long scenicId) {
|
||||
log.debug("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:flat:configs:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) {
|
||||
log.debug("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey());
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.createConfig(scenicId, request);
|
||||
@@ -85,12 +72,6 @@ public class ScenicConfigIntegrationService {
|
||||
return handleResponse(response, "批量更新景区配置失败");
|
||||
}
|
||||
|
||||
public BatchUpdateResponse batchFlatUpdateConfigs(Long scenicId, Map<String, Object> configs) {
|
||||
log.debug("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size());
|
||||
CommonResponse<BatchUpdateResponse> response = scenicConfigV2Client.batchFlatUpdateConfigs(scenicId, configs);
|
||||
return handleResponse(response, "扁平化批量更新景区配置失败");
|
||||
}
|
||||
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
|
@@ -9,7 +9,6 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -42,32 +41,6 @@ public class ScenicIntegrationService {
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) {
|
||||
log.debug("获取景区配置信息, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:config:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<ScenicV2WithConfigDTO> response = scenicV2Client.getScenicWithConfig(scenicId);
|
||||
return handleResponse(response, "获取景区配置信息失败");
|
||||
},
|
||||
ScenicV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> getScenicFlatConfig(Long scenicId) {
|
||||
log.debug("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:flat:config:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicV2DTO createScenic(CreateScenicRequest request) {
|
||||
log.debug("创建景区, name: {}", request.getName());
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.createScenic(request);
|
||||
@@ -98,12 +71,6 @@ public class ScenicIntegrationService {
|
||||
return handleResponse(response, "分页查询景区列表失败");
|
||||
}
|
||||
|
||||
public PageResponse<ScenicV2WithConfigDTO> listScenicsWithConfig(Integer page, Integer pageSize, Integer status, String name) {
|
||||
log.debug("分页查询景区带配置列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
CommonResponse<PageResponse<ScenicV2WithConfigDTO>> response = scenicV2Client.listScenicsWithConfig(page, pageSize, status, name);
|
||||
return handleResponse(response, "分页查询景区带配置列表失败");
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.ycwl.basic.mapper;
|
||||
|
||||
import com.ycwl.basic.model.pc.printer.entity.MemberPrintEntity;
|
||||
import com.ycwl.basic.model.pc.printer.entity.PrintTaskEntity;
|
||||
import com.ycwl.basic.model.pc.printer.entity.PrinterEntity;
|
||||
import com.ycwl.basic.model.pc.printer.resp.MemberPrintResp;
|
||||
@@ -35,7 +36,7 @@ public interface PrinterMapper {
|
||||
|
||||
int deleteUserPhoto(Long memberId, Long scenicId, Long relationId);
|
||||
|
||||
int addUserPhoto(Long memberId, Long scenicId, String url);
|
||||
int addUserPhoto(MemberPrintEntity entity);
|
||||
|
||||
MemberPrintResp getUserPhoto(Long memberId, Long scenicId, Long id);
|
||||
|
||||
|
@@ -1,16 +1,7 @@
|
||||
package com.ycwl.basic.model.pc.scenic.resp;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ycwl.basic.facebody.enums.FaceBodyAdapterType;
|
||||
import com.ycwl.basic.pay.enums.PayAdapterType;
|
||||
import com.ycwl.basic.storage.enums.StorageType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author:longbinbin
|
||||
* @Date:2024/12/2 10:53
|
||||
@@ -19,35 +10,49 @@ import java.util.Date;
|
||||
@Data
|
||||
public class ScenicConfigResp {
|
||||
|
||||
// ========== 基础配置 ==========
|
||||
|
||||
/**
|
||||
* 预约流程,1-预约,2-在线,3-全部
|
||||
* 水印URL
|
||||
*/
|
||||
private Integer bookRoutine;
|
||||
private Integer forceFinishTime;
|
||||
private Integer tourTime;
|
||||
private String watermarkUrl;
|
||||
|
||||
/**
|
||||
* 样本保存时间
|
||||
*/
|
||||
private Integer sampleStoreDay;
|
||||
private Integer faceStoreDay;
|
||||
/**
|
||||
* 视频保存时间
|
||||
* 视频存储天数
|
||||
*/
|
||||
private Integer videoStoreDay;
|
||||
private Boolean allFree;
|
||||
private Boolean disableSourceVideo;
|
||||
private Boolean disableSourceImage;
|
||||
private Integer antiScreenRecordType;
|
||||
private Integer videoSourceStoreDay;
|
||||
private Integer imageSourceStoreDay;
|
||||
private Integer userSourceExpireDay;
|
||||
private BigDecimal brokerDirectRate;
|
||||
|
||||
private String imageSourcePackHint = "";
|
||||
private String videoSourcePackHint = "";
|
||||
private Boolean voucherEnable;
|
||||
private Boolean enableVoucher;
|
||||
/**
|
||||
* 防录屏类型配置
|
||||
*/
|
||||
private Integer antiScreenRecordType;
|
||||
|
||||
// ========== 功能开关 ==========
|
||||
|
||||
/**
|
||||
* 分组功能开关
|
||||
*/
|
||||
private Boolean groupingEnable;
|
||||
|
||||
/**
|
||||
* 优惠券功能开关
|
||||
*/
|
||||
private Boolean voucherEnable;
|
||||
|
||||
/**
|
||||
* 等待时显示照片开关
|
||||
*/
|
||||
private Boolean showPhotoWhenWaiting;
|
||||
private String watermarkUrl;
|
||||
|
||||
// ========== 提示文案 ==========
|
||||
|
||||
/**
|
||||
* 图片素材包提示文案
|
||||
*/
|
||||
private String imageSourcePackHint = "";
|
||||
|
||||
/**
|
||||
* 视频素材包提示文案
|
||||
*/
|
||||
private String videoSourcePackHint = "";
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import com.ycwl.basic.facebody.enums.FaceBodyAdapterType;
|
||||
import com.ycwl.basic.integration.common.util.ConfigValueUtil;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.integration.common.response.PageResponse;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO;
|
||||
@@ -52,8 +51,11 @@ public class ScenicRepository {
|
||||
}
|
||||
|
||||
public ScenicEntity getScenic(Long id) {
|
||||
ScenicV2WithConfigDTO scenicDTO = scenicIntegrationService.getScenicWithConfig(id);
|
||||
ScenicEntity scenicEntity = convertToScenicEntity(scenicDTO);
|
||||
// 分别获取景区基础信息和配置信息
|
||||
ScenicV2DTO scenicBasic = scenicIntegrationService.getScenic(id);
|
||||
ScenicConfigManager configManager = getScenicConfigManager(id);
|
||||
|
||||
ScenicEntity scenicEntity = convertToScenicEntity(scenicBasic, configManager);
|
||||
return scenicEntity;
|
||||
}
|
||||
|
||||
@@ -220,7 +222,7 @@ public class ScenicRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private ScenicEntity convertToScenicEntity(ScenicV2WithConfigDTO dto) {
|
||||
private ScenicEntity convertToScenicEntity(ScenicV2DTO dto, ScenicConfigManager configManager) {
|
||||
if (dto == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -229,19 +231,17 @@ public class ScenicRepository {
|
||||
entity.setName(dto.getName());
|
||||
entity.setMpId(dto.getMpId());
|
||||
entity.setStatus(dto.getStatus().toString());
|
||||
if (dto.getConfig() != null) {
|
||||
entity.setAddress(ConfigValueUtil.getStringValue(dto.getConfig(), "address"));
|
||||
entity.setArea(ConfigValueUtil.getStringValue(dto.getConfig(), "area"));
|
||||
entity.setCity(ConfigValueUtil.getStringValue(dto.getConfig(), "city"));
|
||||
entity.setProvince(ConfigValueUtil.getStringValue(dto.getConfig(), "province"));
|
||||
entity.setLatitude(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "latitude"));
|
||||
entity.setLongitude(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "longitude"));
|
||||
entity.setRadius(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "radius"));
|
||||
entity.setPhone(ConfigValueUtil.getStringValue(dto.getConfig(), "phone"));
|
||||
entity.setLogoUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "logoUrl"));
|
||||
entity.setCoverUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "coverUrl"));
|
||||
entity.setKfCodeUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "kfCodeUrl"));
|
||||
}
|
||||
entity.setAddress(configManager.getString("address"));
|
||||
entity.setArea(configManager.getString("area"));
|
||||
entity.setCity(configManager.getString("city"));
|
||||
entity.setProvince(configManager.getString("province"));
|
||||
entity.setLatitude(configManager.getBigDecimal("latitude"));
|
||||
entity.setLongitude(configManager.getBigDecimal("longitude"));
|
||||
entity.setRadius(configManager.getBigDecimal("radius"));
|
||||
entity.setPhone(configManager.getString("phone"));
|
||||
entity.setLogoUrl(configManager.getString("logo_url"));
|
||||
entity.setCoverUrl(configManager.getString("cover_url"));
|
||||
entity.setKfCodeUrl(configManager.getString("kf_code_url"));
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
@@ -48,7 +48,7 @@ public interface PrinterService {
|
||||
|
||||
PriceObj queryPrice(Long memberId, Long scenicId);
|
||||
|
||||
boolean addUserPhotoFromSource(Long memberId, Long scenicId, FromSourceReq req);
|
||||
List<Integer> addUserPhotoFromSource(Long memberId, Long scenicId, FromSourceReq req);
|
||||
|
||||
Map<String, Object> createOrder(Long memberId, Long scenicId, Integer printerId);
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import com.ycwl.basic.pricing.dto.PriceCalculationResult;
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.service.IPriceCalculationService;
|
||||
import com.ycwl.basic.model.pc.printer.entity.MemberPrintEntity;
|
||||
import com.ycwl.basic.model.pc.printer.entity.PrintTaskEntity;
|
||||
import com.ycwl.basic.model.pc.printer.entity.PrinterEntity;
|
||||
import com.ycwl.basic.model.pc.printer.resp.MemberPrintResp;
|
||||
@@ -44,6 +45,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -190,7 +192,13 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
|
||||
@Override
|
||||
public boolean addUserPhoto(Long memberId, Long scenicId, String url) {
|
||||
printerMapper.addUserPhoto(memberId, scenicId, url);
|
||||
MemberPrintEntity entity = new MemberPrintEntity();
|
||||
entity.setMemberId(memberId);
|
||||
entity.setScenicId(scenicId);
|
||||
entity.setOrigUrl(url);
|
||||
entity.setCropUrl(url);
|
||||
entity.setStatus(0);
|
||||
printerMapper.addUserPhoto(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -259,15 +267,34 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addUserPhotoFromSource(Long memberId, Long scenicId, FromSourceReq req) {
|
||||
public List<Integer> addUserPhotoFromSource(Long memberId, Long scenicId, FromSourceReq req) {
|
||||
List<Integer> resultIds = new ArrayList<>();
|
||||
req.getIds().forEach(id -> {
|
||||
SourceRespVO byId = sourceMapper.getById(id);
|
||||
if (byId == null) {
|
||||
resultIds.add(null);
|
||||
return;
|
||||
}
|
||||
printerMapper.addUserPhoto(memberId, scenicId, byId.getUrl());
|
||||
MemberPrintEntity entity = new MemberPrintEntity();
|
||||
entity.setMemberId(memberId);
|
||||
entity.setScenicId(scenicId);
|
||||
entity.setOrigUrl(byId.getUrl());
|
||||
entity.setCropUrl(byId.getUrl());
|
||||
entity.setStatus(0);
|
||||
|
||||
try {
|
||||
int rows = printerMapper.addUserPhoto(entity);
|
||||
if (rows > 0 && entity.getId() != null) {
|
||||
resultIds.add(entity.getId());
|
||||
} else {
|
||||
resultIds.add(null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("添加用户照片失败, memberId={}, scenicId={}, sourceId={}", memberId, scenicId, id, e);
|
||||
resultIds.add(null);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
return resultIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,16 +1,20 @@
|
||||
package com.ycwl.basic.watchdog;
|
||||
|
||||
import com.ycwl.basic.integration.message.dto.ZtMessage;
|
||||
import com.ycwl.basic.integration.message.service.ZtMessageProducerService;
|
||||
import com.ycwl.basic.mapper.TaskMapper;
|
||||
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
|
||||
import com.ycwl.basic.notify.NotifyFactory;
|
||||
import com.ycwl.basic.notify.entity.NotifyContent;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@Profile("prod")
|
||||
@@ -19,41 +23,145 @@ public class TaskWatchDog {
|
||||
@Autowired
|
||||
private TaskMapper taskMapper;
|
||||
|
||||
@Autowired
|
||||
private ZtMessageProducerService ztMessageProducerService;
|
||||
|
||||
// 异常通知计数器
|
||||
private final Map<String, Integer> notificationCounters = new HashMap<>();
|
||||
|
||||
// 配置参数
|
||||
private static final int MAX_NOTIFICATION_COUNT = 3; // 每种异常最多通知3次
|
||||
|
||||
// 异常类型标识
|
||||
private static final String TASK_BACKLOG = "task_backlog";
|
||||
private static final String FAILED_TASKS = "failed_tasks";
|
||||
private static final String LONG_RUNNING_TASK_PREFIX = "long_running_task_"; // 长时间运行任务前缀
|
||||
|
||||
@Scheduled(fixedDelay = 1000 * 60L)
|
||||
public void scanTaskStatus() {
|
||||
List<TaskEntity> allNotRunningTaskList = taskMapper.selectAllNotRunning();
|
||||
String title = "任务堆积警告!";
|
||||
StringBuilder content = new StringBuilder();
|
||||
if (allNotRunningTaskList.size() > 10) {
|
||||
content.append("当前任务队列中存在超过10个未运行任务,请及时处理!未运行任务数量:").append(allNotRunningTaskList.size());
|
||||
}
|
||||
|
||||
List<TaskEntity> allFailedTaskList = taskMapper.selectAllFailed();
|
||||
if (allFailedTaskList.size() > 5) {
|
||||
if (content.length() > 0) {
|
||||
content.append("\n");
|
||||
}
|
||||
content.append("当前存在超过5个失败任务(status=3),请及时检查和处理!失败任务数量:").append(allFailedTaskList.size());
|
||||
}
|
||||
|
||||
List<TaskEntity> allRunningTaskList = taskMapper.selectAllRunning();
|
||||
for (TaskEntity taskEntity : allRunningTaskList) {
|
||||
|
||||
// 检查任务积压
|
||||
checkTaskBacklog(allNotRunningTaskList);
|
||||
|
||||
// 检查失败任务
|
||||
checkFailedTasks(allFailedTaskList);
|
||||
|
||||
// 检查长时间运行任务
|
||||
checkLongRunningTasks(allRunningTaskList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查任务积压
|
||||
*/
|
||||
private void checkTaskBacklog(List<TaskEntity> notRunningTasks) {
|
||||
if (notRunningTasks.size() > 10) {
|
||||
if (shouldSendNotification(TASK_BACKLOG)) {
|
||||
String content = String.format("当前任务队列中存在超过10个未运行任务,请及时处理!未运行任务数量:%d", notRunningTasks.size());
|
||||
sendNotification("任务堆积警告", content, TASK_BACKLOG);
|
||||
}
|
||||
} else {
|
||||
// 异常已恢复,重置计数器
|
||||
resetNotificationCounter(TASK_BACKLOG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查失败任务
|
||||
*/
|
||||
private void checkFailedTasks(List<TaskEntity> failedTasks) {
|
||||
if (failedTasks.size() > 5) {
|
||||
if (shouldSendNotification(FAILED_TASKS)) {
|
||||
String content = String.format("当前存在超过5个失败任务(status=3),请及时检查和处理!失败任务数量:%d", failedTasks.size());
|
||||
sendNotification("任务失败警告", content, FAILED_TASKS);
|
||||
}
|
||||
} else {
|
||||
// 异常已恢复,重置计数器
|
||||
resetNotificationCounter(FAILED_TASKS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查长时间运行任务
|
||||
*/
|
||||
private void checkLongRunningTasks(List<TaskEntity> runningTasks) {
|
||||
Set<String> currentLongRunningTasks = new HashSet<>();
|
||||
|
||||
for (TaskEntity taskEntity : runningTasks) {
|
||||
if (taskEntity.getStartTime() == null) {
|
||||
continue;
|
||||
}
|
||||
// startTime已经过去3分钟了
|
||||
if (System.currentTimeMillis() - taskEntity.getStartTime().getTime() > 1000 * 60 * 3) {
|
||||
if (content.length() > 0) {
|
||||
content.append("\n");
|
||||
String taskKey = LONG_RUNNING_TASK_PREFIX + taskEntity.getId();
|
||||
currentLongRunningTasks.add(taskKey);
|
||||
|
||||
if (shouldSendNotification(taskKey)) {
|
||||
String content = String.format("当前【%s】渲染机的【%d】任务已超过3分钟未完成!",
|
||||
taskEntity.getWorkerId(), taskEntity.getId());
|
||||
sendNotification("长时间运行任务警告", content, taskKey);
|
||||
}
|
||||
content.append("当前【").append(taskEntity.getWorkerId()).append("】渲染机的【").append(taskEntity.getId()).append("】任务已超过3分钟未完成!");
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(content)) {
|
||||
NotifyFactory.via().sendTo(
|
||||
new NotifyContent(title, content.toString()),
|
||||
"default_user"
|
||||
);
|
||||
|
||||
// 清理已恢复正常的长时运行任务的计数器
|
||||
cleanupLongRunningTaskCounters(currentLongRunningTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已恢复正常的长时运行任务的计数器
|
||||
*/
|
||||
private void cleanupLongRunningTaskCounters(Set<String> currentLongRunningTasks) {
|
||||
Set<String> keysToRemove = new HashSet<>();
|
||||
|
||||
for (String key : notificationCounters.keySet()) {
|
||||
if (key.startsWith(LONG_RUNNING_TASK_PREFIX)) {
|
||||
if (!currentLongRunningTasks.contains(key)) {
|
||||
keysToRemove.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除已恢复任务的计数器
|
||||
for (String key : keysToRemove) {
|
||||
notificationCounters.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该发送通知
|
||||
*/
|
||||
private boolean shouldSendNotification(String abnormalType) {
|
||||
int count = notificationCounters.getOrDefault(abnormalType, 0);
|
||||
return count < MAX_NOTIFICATION_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知并更新计数器
|
||||
*/
|
||||
private void sendNotification(String title, String content, String abnormalType) {
|
||||
ZtMessage ztMessage = ZtMessage.of(
|
||||
"serverchan",
|
||||
title,
|
||||
content,
|
||||
"system"
|
||||
);
|
||||
ztMessage.setSendReason("任务监控");
|
||||
ztMessage.setSendBiz("系统监控");
|
||||
|
||||
ztMessageProducerService.send(ztMessage);
|
||||
|
||||
// 更新通知计数器
|
||||
int currentCount = notificationCounters.getOrDefault(abnormalType, 0);
|
||||
notificationCounters.put(abnormalType, currentCount + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置通知计数器(异常恢复时调用)
|
||||
*/
|
||||
private void resetNotificationCounter(String abnormalType) {
|
||||
notificationCounters.remove(abnormalType);
|
||||
}
|
||||
}
|
||||
|
@@ -95,7 +95,7 @@
|
||||
NOW()
|
||||
)
|
||||
</insert>
|
||||
<insert id="addUserPhoto">
|
||||
<insert id="addUserPhoto" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO member_print (
|
||||
member_id,
|
||||
scenic_id,
|
||||
@@ -108,8 +108,8 @@
|
||||
) VALUES (
|
||||
#{memberId},
|
||||
#{scenicId},
|
||||
#{url},
|
||||
#{url},
|
||||
#{origUrl},
|
||||
#{cropUrl},
|
||||
1,
|
||||
0,
|
||||
NOW(),
|
||||
|
Reference in New Issue
Block a user