skyline преди 3 месеца
родител
ревизия
2eb07f2af8

+ 105 - 0
haha-admin/src/main/java/com/haha/admin/controller/DataSyncController.java

@@ -0,0 +1,105 @@
+package com.haha.admin.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.haha.common.vo.Result;
+import com.haha.entity.SyncRecord;
+import com.haha.service.DataSyncService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 数据同步控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/sync")
+public class DataSyncController {
+
+    @Autowired
+    private DataSyncService dataSyncService;
+
+    /**
+     * 同步设备数据
+     * 从哈哈平台拉取设备列表并存储到本地数据库
+     */
+    @PostMapping("/devices")
+    public Result<SyncRecord> syncDevices() {
+        try {
+            // 获取当前登录用户信息
+            Long operatorId = StpUtil.getLoginIdAsLong();
+            String operatorName = StpUtil.getLoginIdAsString();
+            
+            // 尝试从Session中获取用户名
+            try {
+                Object nickname = StpUtil.getSession().get("nickname");
+                if (nickname != null) {
+                    operatorName = nickname.toString();
+                }
+            } catch (Exception e) {
+                log.debug("获取用户昵称失败,使用默认值");
+            }
+
+            SyncRecord record = dataSyncService.syncDevices(operatorId, operatorName);
+            
+            if (record.getStatus() == 3) {
+                return Result.error("同步失败: " + record.getErrorMessage());
+            }
+            return Result.success(record);
+        } catch (Exception e) {
+            log.error("同步设备数据失败", e);
+            return Result.error("同步失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取同步记录列表
+     */
+    @GetMapping("/records")
+    public Result<Map<String, Object>> getSyncRecords(
+            @RequestParam(required = false) String syncType,
+            @RequestParam(required = false) Integer status,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            Map<String, Object> result = dataSyncService.getSyncRecordList(syncType, status, page, pageSize);
+            return Result.success(result);
+        } catch (Exception e) {
+            log.error("获取同步记录列表失败", e);
+            return Result.error("获取同步记录失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取同步日志列表
+     */
+    @GetMapping("/logs/{recordId}")
+    public Result<Map<String, Object>> getSyncLogs(
+            @PathVariable Long recordId,
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            Map<String, Object> result = dataSyncService.getSyncLogList(recordId, page, pageSize);
+            return Result.success(result);
+        } catch (Exception e) {
+            log.error("获取同步日志列表失败", e);
+            return Result.error("获取同步日志失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取同步统计信息
+     */
+    @GetMapping("/statistics")
+    public Result<Map<String, Object>> getSyncStatistics() {
+        try {
+            Map<String, Object> stats = dataSyncService.getSyncStatistics();
+            return Result.success(stats);
+        } catch (Exception e) {
+            log.error("获取同步统计信息失败", e);
+            return Result.error("获取统计信息失败: " + e.getMessage());
+        }
+    }
+}

+ 4 - 0
haha-entity/src/main/java/com/haha/entity/Device.java

@@ -26,4 +26,8 @@ public class Device implements Serializable {
     private Integer status;
 
     private String currentInventoryHash;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
 }

+ 63 - 0
haha-entity/src/main/java/com/haha/entity/SyncLog.java

@@ -0,0 +1,63 @@
+package com.haha.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 数据同步日志表
+ */
+@Data
+@TableName("t_sync_log")
+public class SyncLog implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 同步记录ID
+     */
+    private Long recordId;
+
+    /**
+     * 同步类型:DEVICE-设备,PRODUCT-商品,ORDER-订单
+     */
+    private String syncType;
+
+    /**
+     * 数据ID(设备ID、商品ID等)
+     */
+    private String dataId;
+
+    /**
+     * 数据名称
+     */
+    private String dataName;
+
+    /**
+     * 同步状态:0-失败,1-成功
+     */
+    private Integer status;
+
+    /**
+     * 同步结果消息
+     */
+    private String message;
+
+    /**
+     * 同步详情(JSON格式)
+     */
+    private String detail;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 83 - 0
haha-entity/src/main/java/com/haha/entity/SyncRecord.java

@@ -0,0 +1,83 @@
+package com.haha.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 数据同步记录表
+ */
+@Data
+@TableName("t_sync_record")
+public class SyncRecord implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 同步类型:DEVICE-设备,PRODUCT-商品,ORDER-订单
+     */
+    private String syncType;
+
+    /**
+     * 同步状态:0-待同步,1-同步中,2-同步成功,3-同步失败
+     */
+    private Integer status;
+
+    /**
+     * 同步数据总数
+     */
+    private Integer totalCount;
+
+    /**
+     * 同步成功数量
+     */
+    private Integer successCount;
+
+    /**
+     * 同步失败数量
+     */
+    private Integer failCount;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 同步耗时(毫秒)
+     */
+    private Long duration;
+
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 操作人ID
+     */
+    private Long operatorId;
+
+    /**
+     * 操作人名称
+     */
+    private String operatorName;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 7 - 0
haha-mapper/src/main/java/com/haha/mapper/SyncLogMapper.java

@@ -0,0 +1,7 @@
+package com.haha.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.haha.entity.SyncLog;
+
+public interface SyncLogMapper extends BaseMapper<SyncLog> {
+}

+ 7 - 0
haha-mapper/src/main/java/com/haha/mapper/SyncRecordMapper.java

@@ -0,0 +1,7 @@
+package com.haha.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.haha.entity.SyncRecord;
+
+public interface SyncRecordMapper extends BaseMapper<SyncRecord> {
+}

+ 52 - 0
haha-service/src/main/java/com/haha/service/DataSyncService.java

@@ -0,0 +1,52 @@
+package com.haha.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.haha.entity.SyncRecord;
+import com.haha.entity.SyncLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 数据同步服务接口
+ */
+public interface DataSyncService extends IService<SyncRecord> {
+
+    /**
+     * 同步设备列表
+     * 从哈哈平台拉取设备数据并存储到本地数据库
+     * 
+     * @param operatorId 操作人ID
+     * @param operatorName 操作人名称
+     * @return 同步记录
+     */
+    SyncRecord syncDevices(Long operatorId, String operatorName);
+
+    /**
+     * 获取同步记录列表
+     * 
+     * @param syncType 同步类型(可选)
+     * @param status 同步状态(可选)
+     * @param page 页码
+     * @param pageSize 每页数量
+     * @return 分页结果
+     */
+    Map<String, Object> getSyncRecordList(String syncType, Integer status, Integer page, Integer pageSize);
+
+    /**
+     * 获取同步日志列表
+     * 
+     * @param recordId 同步记录ID
+     * @param page 页码
+     * @param pageSize 每页数量
+     * @return 分页结果
+     */
+    Map<String, Object> getSyncLogList(Long recordId, Integer page, Integer pageSize);
+
+    /**
+     * 获取同步统计信息
+     * 
+     * @return 统计信息
+     */
+    Map<String, Object> getSyncStatistics();
+}

+ 251 - 0
haha-service/src/main/java/com/haha/service/impl/DataSyncServiceImpl.java

@@ -0,0 +1,251 @@
+package com.haha.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.haha.entity.Device;
+import com.haha.entity.SyncLog;
+import com.haha.entity.SyncRecord;
+import com.haha.mapper.SyncLogMapper;
+import com.haha.mapper.SyncRecordMapper;
+import com.haha.sdk.HahaClient;
+import com.haha.sdk.exception.HahaException;
+import com.haha.sdk.model.DeviceInfo;
+import com.haha.service.DataSyncService;
+import com.haha.service.DeviceService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 数据同步服务实现
+ */
+@Slf4j
+@Service
+public class DataSyncServiceImpl extends ServiceImpl<SyncRecordMapper, SyncRecord> implements DataSyncService {
+
+    @Autowired
+    private HahaClient hahaClient;
+
+    @Autowired
+    private DeviceService deviceService;
+
+    @Autowired
+    private SyncLogMapper syncLogMapper;
+
+    /**
+     * 同步状态常量
+     */
+    private static final int STATUS_PENDING = 0;      // 待同步
+    private static final int STATUS_SYNCING = 1;      // 同步中
+    private static final int STATUS_SUCCESS = 2;      // 同步成功
+    private static final int STATUS_FAILED = 3;       // 同步失败
+
+    /**
+     * 同步类型
+     */
+    private static final String SYNC_TYPE_DEVICE = "DEVICE";
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public SyncRecord syncDevices(Long operatorId, String operatorName) {
+        log.info("开始同步设备数据,操作人: {}", operatorName);
+
+        // 1. 创建同步记录
+        SyncRecord record = new SyncRecord();
+        record.setSyncType(SYNC_TYPE_DEVICE);
+        record.setStatus(STATUS_SYNCING);
+        record.setStartTime(LocalDateTime.now());
+        record.setOperatorId(operatorId);
+        record.setOperatorName(operatorName);
+        record.setCreateTime(LocalDateTime.now());
+        record.setTotalCount(0);
+        record.setSuccessCount(0);
+        record.setFailCount(0);
+        save(record);
+
+        int successCount = 0;
+        int failCount = 0;
+        StringBuilder errorMsg = new StringBuilder();
+
+        try {
+            // 2. 从哈哈平台获取设备列表
+            List<DeviceInfo> deviceList = hahaClient.getDeviceApi().getDeviceList(1, 100);
+            record.setTotalCount(deviceList.size());
+            log.info("从哈哈平台获取到 {} 台设备", deviceList.size());
+
+            // 3. 遍历设备列表,同步到本地数据库
+            for (DeviceInfo deviceInfo : deviceList) {
+                try {
+                    syncSingleDevice(record.getId(), deviceInfo);
+                    successCount++;
+                } catch (Exception e) {
+                    failCount++;
+                    String errMsg = String.format("设备 %s 同步失败: %s", deviceInfo.getId(), e.getMessage());
+                    errorMsg.append(errMsg).append("; ");
+                    log.error(errMsg, e);
+
+                    // 记录失败日志
+                    saveSyncLog(record.getId(), SYNC_TYPE_DEVICE, deviceInfo.getId(), 
+                               deviceInfo.getName(), 0, errMsg, JSON.toJSONString(deviceInfo));
+                }
+            }
+
+            // 4. 更新同步记录
+            record.setSuccessCount(successCount);
+            record.setFailCount(failCount);
+            record.setStatus(failCount == 0 ? STATUS_SUCCESS : (successCount > 0 ? STATUS_SUCCESS : STATUS_FAILED));
+            record.setEndTime(LocalDateTime.now());
+            record.setDuration(java.time.Duration.between(record.getStartTime(), record.getEndTime()).toMillis());
+            if (errorMsg.length() > 0) {
+                record.setErrorMessage(errorMsg.toString());
+            }
+            updateById(record);
+
+            log.info("设备数据同步完成,成功: {}, 失败: {}", successCount, failCount);
+
+        } catch (HahaException e) {
+            log.error("调用哈哈平台API失败", e);
+            record.setStatus(STATUS_FAILED);
+            record.setEndTime(LocalDateTime.now());
+            record.setDuration(java.time.Duration.between(record.getStartTime(), record.getEndTime()).toMillis());
+            record.setErrorMessage("调用哈哈平台API失败: " + e.getMessage());
+            updateById(record);
+        } catch (Exception e) {
+            log.error("设备数据同步异常", e);
+            record.setStatus(STATUS_FAILED);
+            record.setEndTime(LocalDateTime.now());
+            record.setDuration(java.time.Duration.between(record.getStartTime(), record.getEndTime()).toMillis());
+            record.setErrorMessage("同步异常: " + e.getMessage());
+            updateById(record);
+        }
+
+        return record;
+    }
+
+    /**
+     * 同步单个设备
+     */
+    private void syncSingleDevice(Long recordId, DeviceInfo deviceInfo) {
+        log.debug("同步设备: {} - {}", deviceInfo.getId(), deviceInfo.getName());
+
+        // 查询本地是否已存在该设备
+        Device existDevice = deviceService.getDeviceBySn(deviceInfo.getId());
+
+        if (existDevice != null) {
+            // 更新设备信息
+            existDevice.setName(deviceInfo.getName());
+            existDevice.setStatus("1".equals(deviceInfo.getStatus()) ? 1 : 0);
+            // existDevice.setAddress(deviceInfo.getAddress()); // Device实体暂无address字段
+            deviceService.updateById(existDevice);
+
+            // 记录成功日志
+            saveSyncLog(recordId, SYNC_TYPE_DEVICE, deviceInfo.getId(), 
+                       deviceInfo.getName(), 1, "设备信息更新成功", null);
+        } else {
+            // 新增设备
+            Device newDevice = new Device();
+            newDevice.setDeviceId(deviceInfo.getId());
+            newDevice.setName(deviceInfo.getName());
+            // newDevice.setAddress(deviceInfo.getAddress()); // Device实体暂无address字段
+            newDevice.setStatus("1".equals(deviceInfo.getStatus()) ? 1 : 0);
+            deviceService.save(newDevice);
+
+            // 记录成功日志
+            saveSyncLog(recordId, SYNC_TYPE_DEVICE, deviceInfo.getId(), 
+                       deviceInfo.getName(), 1, "设备新增成功", null);
+        }
+    }
+
+    /**
+     * 保存同步日志
+     */
+    private void saveSyncLog(Long recordId, String syncType, String dataId, 
+                            String dataName, Integer status, String message, String detail) {
+        SyncLog syncLog = new SyncLog();
+        syncLog.setRecordId(recordId);
+        syncLog.setSyncType(syncType);
+        syncLog.setDataId(dataId);
+        syncLog.setDataName(dataName);
+        syncLog.setStatus(status);
+        syncLog.setMessage(message);
+        syncLog.setDetail(detail);
+        syncLog.setCreateTime(LocalDateTime.now());
+        syncLogMapper.insert(syncLog);
+    }
+
+    @Override
+    public Map<String, Object> getSyncRecordList(String syncType, Integer status, Integer page, Integer pageSize) {
+        LambdaQueryWrapper<SyncRecord> wrapper = new LambdaQueryWrapper<>();
+        if (syncType != null && !syncType.isEmpty()) {
+            wrapper.eq(SyncRecord::getSyncType, syncType);
+        }
+        if (status != null) {
+            wrapper.eq(SyncRecord::getStatus, status);
+        }
+        wrapper.orderByDesc(SyncRecord::getCreateTime);
+
+        IPage<SyncRecord> pageResult = page(new Page<>(page, pageSize), wrapper);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("list", pageResult.getRecords());
+        result.put("total", pageResult.getTotal());
+        result.put("page", page);
+        result.put("pageSize", pageSize);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getSyncLogList(Long recordId, Integer page, Integer pageSize) {
+        LambdaQueryWrapper<SyncLog> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SyncLog::getRecordId, recordId);
+        wrapper.orderByDesc(SyncLog::getCreateTime);
+
+        IPage<SyncLog> pageResult = syncLogMapper.selectPage(new Page<>(page, pageSize), wrapper);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("list", pageResult.getRecords());
+        result.put("total", pageResult.getTotal());
+        result.put("page", page);
+        result.put("pageSize", pageSize);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> getSyncStatistics() {
+        Map<String, Object> stats = new HashMap<>();
+
+        // 总同步次数
+        long totalRecords = count();
+        stats.put("totalRecords", totalRecords);
+
+        // 成功次数
+        long successRecords = lambdaQuery().eq(SyncRecord::getStatus, STATUS_SUCCESS).count();
+        stats.put("successRecords", successRecords);
+
+        // 失败次数
+        long failedRecords = lambdaQuery().eq(SyncRecord::getStatus, STATUS_FAILED).count();
+        stats.put("failedRecords", failedRecords);
+
+        // 设备同步统计
+        long deviceSyncCount = lambdaQuery().eq(SyncRecord::getSyncType, SYNC_TYPE_DEVICE).count();
+        stats.put("deviceSyncCount", deviceSyncCount);
+
+        // 最近一次同步记录
+        SyncRecord latestRecord = lambdaQuery()
+                .orderByDesc(SyncRecord::getCreateTime)
+                .last("LIMIT 1")
+                .one();
+        stats.put("latestRecord", latestRecord);
+
+        return stats;
+    }
+}

+ 1 - 0
pom.xml

@@ -130,6 +130,7 @@
                         <source>${java.version}</source>
                         <target>${java.version}</target>
                         <encoding>${project.build.sourceEncoding}</encoding>
+                        <parameters>true</parameters>
                     </configuration>
                 </plugin>
             </plugins>