新疆后端项目

This commit is contained in:
DESKTOP-BLB5287\FP
2025-06-30 14:31:09 +08:00
commit 0e52aff1f1
2596 changed files with 261030 additions and 0 deletions
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>data-center-api</artifactId>
<version>2.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,72 @@
package com.renkang.datacenter.api;
import com.renkang.datacenter.bean.*;
import com.renkang.datacenter.config.ImcCenterConstants;
import org.jeecg.common.api.vo.Result;
import org.springframework.retry.annotation.Retryable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Retryable(maxAttempts = 2, listeners = "restRetryListener")
public interface DataCenterExecutor {
Result<Object> addDevice(WatchDevice device);
Result<Object> addDeviceBatch(WatchDeviceBatch devices);
Result<Object> removeDevice(WatchNo watchNo);
Result<Object> setSwitchStatus(WatchDeviceSwitch watchDeviceSwitch);
Result<Object> setSwitchStatusAll(DeviceSwitch deviceSwitch);
Result<Object> cmdNotice(Notice notice);
Result<Object> cmdNoticeMulti(Notice notice);
Result<Object> watchDataList(WatchDataList watchDataList);
Result<Object> increaseWatchDataCount(IncreaseWatchDataCount dataCount);
Result<Object> increaseWatchDataList(IncreaseWatchDataList dataList);
Result<Object> watchSdcItemData(WatchSdcItemData sdcItemData);
Result<Object> deviceInfoAll(WatchNo watchNo);
Result<Object> getRate(WatchNo watchNo);
Result<Object> getPeriod(WatchNo watchNo);
Result<Object> getWarnSwitch(WatchNo watchNo);
Result<Object> saveRate(Rate rate);
Result<Object> savePeriod(Period period);
Result<Object> saveWarnSwitch(WarnSwitch warnSwitch);
Result<Object> cmdDataPush(String watchNo);
Result<Object> cmdUpdatePeriod(String watchNo);
Result<Object> cmdUpdateTask(String watchNo);
Result<Object> cmdUpdateSwitch(String watchNo);
@Retryable(maxAttempts = 2, listeners = ImcCenterConstants.IMC_LISTENER)
Result<Object> getImageMessage(ImcPeIdVo imcPeIdVo);
/**
* 通过peId获取影像基本信息
* @param imcPeIdVo
* @return
*/
@Retryable(maxAttempts = 2, listeners = ImcCenterConstants.IMC_LISTENER)
Result<Object> getImageItems(ImcPeIdVo imcPeIdVo);
String downloadAsString(String filepath);
}
@@ -0,0 +1,185 @@
package com.renkang.datacenter.api;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.renkang.datacenter.bean.*;
import com.renkang.datacenter.config.DataCenterConstants;
import com.renkang.datacenter.config.DataCenterProperties;
import com.renkang.datacenter.config.MethodApi;
import com.renkang.datacenter.util.RemoteUtils;
import org.jeecg.common.api.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Objects;
@Service
public class DefaultDataCenterExecutor implements DataCenterExecutor {
private DataCenterProperties properties;
@Autowired
public void setProperties(DataCenterProperties properties) {
this.properties = properties;
}
@Override
public Result<Object> addDevice(WatchDevice device) {
return RemoteUtils.post(MethodApi.ADD_DEVICE,device);
}
@Override
public Result<Object> addDeviceBatch(WatchDeviceBatch devices) {
return RemoteUtils.post(MethodApi.ADD_DEVICE_BATCH,devices);
}
@Override
public Result<Object> removeDevice(WatchNo watchNo) {
return RemoteUtils.post(MethodApi.REMOVE_DEVICE, objectToMap(watchNo));
}
@Override
public Result<Object> setSwitchStatus(WatchDeviceSwitch watchDeviceSwitch) {
return RemoteUtils.post(MethodApi.SET_SWITCH_STATUS, watchDeviceSwitch);
}
@Override
public Result<Object> setSwitchStatusAll(DeviceSwitch deviceSwitch) {
return RemoteUtils.post(MethodApi.SET_SWITCH_STATUS_ALL, deviceSwitch);
}
@Override
public Result<Object> cmdNotice(Notice notice) {
return RemoteUtils.post(MethodApi.CMD_NOTICE, objectToMap(notice));
}
@Override
public Result<Object> cmdNoticeMulti(Notice notice) {
return RemoteUtils.post(MethodApi.CMD_NOTICE_MULTI, objectToMap(notice));
}
@Override
public Result<Object> watchDataList(WatchDataList watchDataList) {
return RemoteUtils.get(MethodApi.WATCH_DATA_LIST, objectToMap(watchDataList));
}
@Override
public Result<Object> increaseWatchDataCount(IncreaseWatchDataCount dataCount) {
if (Objects.isNull(dataCount.getPageSize()) || dataCount.getPageSize() == 0) {
dataCount.setPageSize(properties.getPageSize());
}
return RemoteUtils.get(MethodApi.INCREASE_WATCH_DATA_PAGE_COUNT, objectToMap(dataCount));
}
@Override
public Result<Object> increaseWatchDataList(IncreaseWatchDataList dataList) {
if (Objects.isNull(dataList.getPageSize()) || dataList.getPageSize() == 0) {
dataList.setPageSize(properties.getPageSize());
}
return RemoteUtils.get(MethodApi.INCREASE_WATCH_DATA_PAGE_LIST, objectToMap(dataList));
}
@Override
public Result<Object> watchSdcItemData(WatchSdcItemData sdcItemData) {
return RemoteUtils.get(MethodApi.WATCH_SDC_ITEM_DATA, objectToMap(sdcItemData));
}
@Override
public Result<Object> deviceInfoAll(WatchNo watchNo) {
return RemoteUtils.get(MethodApi.DEVICE_INFO_ALL, objectToMap(watchNo));
}
@Override
public Result<Object> getRate(WatchNo watchNo) {
return RemoteUtils.get(MethodApi.GET_RATE, objectToMap(watchNo));
}
@Override
public Result<Object> getPeriod(WatchNo watchNo) {
return RemoteUtils.get(MethodApi.GET_PERIOD, objectToMap(watchNo));
}
@Override
public Result<Object> getWarnSwitch(WatchNo watchNo) {
return RemoteUtils.get(MethodApi.GET_WARN_SWITCH, objectToMap(watchNo));
}
@Override
public Result<Object> saveRate(Rate rate) {
return RemoteUtils.post(MethodApi.SAVE_RATE, rate);
}
@Override
public Result<Object> savePeriod(Period period) {
return RemoteUtils.post(MethodApi.SAVE_PERIOD, period);
}
@Override
public Result<Object> saveWarnSwitch(WarnSwitch warnSwitch) {
return RemoteUtils.post(MethodApi.SAVE_WARN_SWITCH, warnSwitch);
}
@Override
public Result<Object> cmdDataPush(String watchNo) {
MqttTask task = new MqttTask();
task.setWatchNo(watchNo);
task.setTaskName(DataCenterConstants.MQTT_CMD_DATA_PUSH);
return cmdDataPush(task);
}
@Override
public Result<Object> cmdUpdatePeriod(String watchNo) {
MqttTask task = new MqttTask();
task.setWatchNo(watchNo);
task.setTaskName(DataCenterConstants.MQTT_CMD_UPDATE_PERIOD);
return cmdDataPush(task);
}
@Override
public Result<Object> cmdUpdateTask(String watchNo) {
MqttTask task = new MqttTask();
task.setWatchNo(watchNo);
task.setTaskName(DataCenterConstants.MQTT_CMD_UPDATE_TASK);
return cmdDataPush(task);
}
@Override
public Result<Object> cmdUpdateSwitch(String watchNo) {
MqttTask task = new MqttTask();
task.setWatchNo(watchNo);
task.setTaskName(DataCenterConstants.MQTT_CMD_UPDATE_SWITCH);
return cmdDataPush(task);
}
@Override
public Result<Object> getImageMessage(ImcPeIdVo imcPeIdVo) {
return RemoteUtils.post(MethodApi.GET_IMAGE_MESSAGE, imcPeIdVo);
}
/**
* 通过peId获取影像基本信息
*
* @param imcPeIdVo
* @return
*/
@Override
public Result<Object> getImageItems(ImcPeIdVo imcPeIdVo) {
return RemoteUtils.post(MethodApi.GET_IMAGE_ITEMS, imcPeIdVo);
}
@Override
public String downloadAsString(String filepath) {
return RemoteUtils.downloadAsString(filepath);
}
private Result<Object> cmdDataPush(MqttTask task) {
return RemoteUtils.post(MethodApi.CMD_DATA_PUSH, objectToMap(task));
}
private Map<String,Object> objectToMap(Object source) {
return (JSONObject) JSON.toJSON(source);
}
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataCommon extends WatchDataBase {
private String wdType;
private Double dataValue;
private String timeStamp;
}
@@ -0,0 +1,40 @@
package com.renkang.datacenter.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
/**
* @author Jiang Shunzhi
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataEcg extends WatchDataBase {
private Long ecgStartTime;
private Long ecgEndTime;
@JSONField(format = "yyyy-MM-dd")
private Date dataDate;
private Integer ecgArrhyType;
private String ecgArrhyTypeDict;
private Integer ecgUserSymptom;
private String ecgUserSymptomDict;
private Integer ecgArrhyAvgRate;
private Integer ecgDataPointsCount;
private String ecgDataPoints;
}
@@ -0,0 +1,29 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataExercise extends WatchDataBase {
/**
* 中高强度运动时间(分钟)
*/
private Integer strengthTimes;
/**
* 总活动时长(小时)
*/
private Integer totalTime;
/**
* 数据日期
*/
private String dataDate;
}
@@ -0,0 +1,24 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataGps extends WatchDataBase {
private Double lon;
private Double lat;
private String address;
private String timeStamp;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataHeartRate extends WatchDataBase {
private Double dataValue;
private Double silenceValue;
private String timeStamp;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataSdc extends WatchDataBase {
private String sdcDate;
private Long dataValue;
private String wdType;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataSleep extends WatchDataBase {
private String startTimeStamp;
private String endTimeStamp;
private String sleepType;
}
@@ -0,0 +1,109 @@
package com.renkang.datacenter.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataSleepNew extends WatchDataBase {
private String sleepFlag;
@JSONField(alternateNames = "DataDate")
private String dataDate;
private Integer errCode;
private List<Error> errCodeArr;
private List<Day> statusInDayArr;
private List<Minute> statusInMinuteArr;
@Data
public static class Error implements Serializable {
private Integer errCode;
private String startTime;
private String endTime;
}
@Data
public static class Day implements Serializable {
/**
* 睡梦时长(分钟)
*/
private Integer deepSleepPartCnt;
/**
* 入睡时间点
*/
private String fallAsleepTime;
/**
* 上床时间
*/
private String goBedTime;
/**
* 睡眠效率百分比
*/
private Integer sleepEfficiency;
/**
* 睡眠潜伏期
*/
private Long sleepLatency;
/**
* 睡眠得分
*/
private Integer sleepScore;
/**
* 原始睡眠得分
*/
@JSONField(alternateNames = "sleepScoreOrign")
private Integer sleepScoreOrigin;
/**
* 鼾声(每小时次数)
*/
private Integer snoreFreq;
/**
* 当天的时间戳
*/
private String startTime;
/**
* 数据有效性
*/
private Double validData;
/**
* 醒来时间
*/
private String wakeUpTime;
}
@Data
public static class Minute implements Serializable {
/**
* 睡眠状态
*/
private String status;
/**
* 开始时间
*/
private String startTime;
/**
* 结束时间
*/
private String endTime;
}
}
@@ -0,0 +1,27 @@
package com.renkang.datacenter.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataSteps extends WatchDataBase {
/**
* 数据日期
*/
@JSONField(format = "yyyy-MM-dd")
private Date stepDate;
private Integer stepCount;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataStress extends WatchDataBase {
private Double dataValue;
private String startTimeStamp;
private String endTimeStamp;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataTemperature extends WatchDataBase {
private Double skinTempera;
private Double dataValue;
private String timeStamp;
}
@@ -0,0 +1,38 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataTrace extends WatchDataBase {
private String recordId;
/**
* 海拔
*/
private Double altitude;
/**
* 纬度
*/
private Double latitude;
/**
* 经度
*/
private Double longitude;
/**
* 时间戳
*/
private String utcTime;
/**
* 数据是否合法
*/
private String valid;
}
@@ -0,0 +1,25 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DataWorkout extends WatchDataBase {
private String workoutType;
private Double calorie;
private Double distance;
private String startTimeStamp;
private String endTimeStamp;
}
@@ -0,0 +1,17 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class DeviceSwitch implements Serializable {
private String wdType;
private String switchFlag;
}
@@ -0,0 +1,11 @@
package com.renkang.datacenter.bean;
import lombok.Data;
@Data
public class ImcPeIdVo {
private String peId;
private String hospitalId;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class IncreaseWatchDataCount implements Serializable {
private String wdType;
private String id;
private Integer pageSize;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class IncreaseWatchDataList extends IncreaseWatchDataCount implements Serializable {
private Integer pageNo;
}
@@ -0,0 +1,14 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class Login implements Serializable {
private String username;
private String password;
}
@@ -0,0 +1,18 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class MqttTask implements Serializable {
private String watchNo;
private String taskName;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class Notice implements Serializable {
private String noticeTitle;
private String notice;
private String watchNo;
}
@@ -0,0 +1,24 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class Period implements Serializable {
private String watchNo;
private String wdType;
private Double warnMin;
private Double warnMax;
private Integer period;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class Rate implements Serializable {
private String watchNo;
private String wdType;
private Integer taskRate;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class WarnSwitch implements Serializable {
private String watchNo;
private String wdType;
private String switchFlag;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@Data
public class WatchDataBase implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String watchNo;
}
@@ -0,0 +1,18 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class WatchDataList implements Serializable {
private String watchNo;
private String queryDate;
}
@@ -0,0 +1,57 @@
package com.renkang.datacenter.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @author Shunzhi Jiang
* @since 2023/11/13
*/
@Data
public class WatchDataResult implements Serializable {
private String maxId;
@JSONField(alternateNames = "commonVoList")
private List<DataCommon> commonList;
@JSONField(alternateNames = "heartRateVoList")
private List<DataHeartRate> heartRateList;
@JSONField(alternateNames = "gpsVoList")
private List<DataGps> gpsList;
@JSONField(alternateNames = "sleepVoList")
private List<DataSleep> sleepList;
@JSONField(alternateNames = "sdcVoList")
private List<DataSdc> sdcList;
@JSONField(alternateNames = "tempVoList")
private List<DataTemperature> tempList;
@JSONField(alternateNames = "workoutVoList")
private List<DataWorkout> workoutList;
@JSONField(alternateNames = "stressVoList")
private List<DataStress> stressList;
@JSONField(alternateNames = "exerciseVoList")
private List<DataExercise> exerciseList;
@JSONField(alternateNames = "traceVoList")
private List<DataTrace> traceList;
@JSONField(alternateNames = "sleepNewVoList")
private List<DataSleepNew> sleepNewList;
@JSONField(alternateNames = "ecgVoList")
private List<DataEcg> ecgList;
@JSONField(alternateNames = "stepVoList")
private List<DataSteps> stepList;
}
@@ -0,0 +1,24 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class WatchDevice implements Serializable {
/**
* 手表SN编码
*/
private String watchNo;
/**
* IMEI编码
*/
private String imeiNo;
/**
* EID编码
*/
private String eid;
private String watchModel;
}
@@ -0,0 +1,17 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class WatchDeviceBatch implements Serializable {
private List<WatchDevice> deviceVoList;
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class WatchDeviceSwitch extends DeviceSwitch implements Serializable {
private String watchNo;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class WatchNo implements Serializable {
private String watchNo;
public static WatchNo of(String watchNo) {
WatchNo instance = new WatchNo();
instance.setWatchNo(watchNo);
return instance;
}
}
@@ -0,0 +1,20 @@
package com.renkang.datacenter.bean;
import lombok.Data;
import java.io.Serializable;
/**
* @author Shunzhi Jiang
* @since 2023/11/5
*/
@Data
public class WatchSdcItemData implements Serializable {
private String watchNo;
private String wdType;
private String queryDate;
}
@@ -0,0 +1,22 @@
package com.renkang.datacenter.config;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
public interface DataCenterConstants {
//通知更新阈值及频率
String MQTT_CMD_UPDATE_PERIOD = "updatePeriod";
//通知更新定时任务
String MQTT_CMD_UPDATE_TASK = "updateTask";
//通知更新开关
String MQTT_CMD_UPDATE_SWITCH = "updateSwitch";
//通知手表上传数据
String MQTT_CMD_DATA_PUSH = "dataPush";
String REDIS_TOKEN_KEY = "DC_TOKEN";
String HEADER_TOKEN_KEY = "X-Access-Token";
}
@@ -0,0 +1,28 @@
package com.renkang.datacenter.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
@Component
@ConfigurationProperties("datacenter")
@Data
public class DataCenterProperties {
private String url = "";
private String username;
private String password;
private String publicKey;
private Long timeout = 3000L;
private Integer pageSize = 500;
}
@@ -0,0 +1,12 @@
package com.renkang.datacenter.config;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
public interface ImcCenterConstants {
String IMC_LISTENER = "RestRetryImcListener";
String REDIS_TOKEN_KEY = "IMC_TOKEN";
}
@@ -0,0 +1,109 @@
package com.renkang.datacenter.config;
import lombok.Getter;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
@Getter
public enum MethodApi {
/**
* 获取令牌
*/
GET_TOKEN("sys/getToken"),
/**
* 添加设备
*/
ADD_DEVICE("watch/api/addDevice"),
/**
* 批量添加设备
*/
ADD_DEVICE_BATCH("watch/api/batchAddDevice"),
/**
* 删除设备
*/
REMOVE_DEVICE("watch/api/removeDevice?watchNo={watchNo}"),
/**
* 设备开关设置
*/
SET_SWITCH_STATUS("watch/api/setSwitchStatus"),
/**
* 设备开关全部设置
*/
SET_SWITCH_STATUS_ALL("watch/api/setAllSwitchStatus"),
/**
* 通知消息
*/
CMD_NOTICE("watch/mqtt/cmdNotice?watchNo={watchNo}&noticeTitle={noticeTitle}&notice={notice}"),
/**
* 通知消息
*/
CMD_NOTICE_MULTI("watch/mqtt/cmdNoticeMulti?watchNo={watchNo}&noticeTitle={noticeTitle}&notice={notice}"),
/**
* 推送数据通知
*/
CMD_DATA_PUSH("watch/mqtt/cmdDataPush?watchNo={watchNo}&taskName={taskName}"),
/**
* 全量数据获取
*/
WATCH_DATA_LIST("watch/api/getWatchDataList/{queryDate}?watchNo={watchNo}"),
/**
* 增量数据获取(页数)
*/
INCREASE_WATCH_DATA_PAGE_COUNT("watch/api/getIncreWatchDataPageCount/{wdType}/{id}/{pageSize}"),
/**
* 增量数据获取(数据)
*/
INCREASE_WATCH_DATA_PAGE_LIST("watch/api/getIncreWatchDataPageList/{wdType}/{id}/{pageNo}/{pageSize}"),
/**
* SDC详细数据获取
*/
WATCH_SDC_ITEM_DATA("watch/api/getWatchSdcItemData/{watchNo}/{queryDate}/{wdType}"),
/**
* 设备列表查询
*/
DEVICE_INFO_ALL("watch/api/getAllDeviceInfo?watchNo={watchNo}"),
/**
* 查询上传频率设置
*/
GET_RATE("watch/api/getRateByWatchNo?watchNo={watchNo}"),
/**
* 设置上传频率
*/
SAVE_RATE("watch/api/saveRateByNoAndWdType"),
/**
* 查询阈值设置
*/
GET_PERIOD("watch/api/getPeriodByWatchNo?watchNo={watchNo}"),
/**
* 设置阈值
*/
SAVE_PERIOD("watch/api/savePeriodByNoAndWdType"),
/**
* 查询报警开关设置
*/
GET_WARN_SWITCH("watch/api/getWarnListByWatchNo?watchNo={watchNo}"),
/**
* 设置报警开关
*/
SAVE_WARN_SWITCH("watch/api/saveWarnByNoAndWdType"),
// =======================影像中心============================
/**
* 查询影像人员信息
*/
GET_IMAGE_MESSAGE("imc/file/getImageMsg"),
/**
* 通过peId获取影像基本信息
*/
GET_IMAGE_ITEMS("imc/file/getImageItems"),
;
private final String url;
MethodApi(String url) {
this.url = url;
}
}
@@ -0,0 +1,40 @@
package com.renkang.datacenter.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
@Configuration
@EnableRetry
public class RestConfig {
private final DataCenterProperties properties;
public RestConfig(DataCenterProperties properties) {
this.properties = properties;
}
@Bean
public RestTemplate dataCenterTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.of(properties.getTimeout(), ChronoUnit.MILLIS))
.additionalMessageConverters(
new StringHttpMessageConverter(StandardCharsets.UTF_8),
new MappingJackson2HttpMessageConverter()
)
.build();
}
}
@@ -0,0 +1,88 @@
package com.renkang.datacenter.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.renkang.datacenter.bean.Login;
import com.renkang.datacenter.util.RemoteUtils;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.util.RSAEncryptUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.interceptor.MethodInvocationRetryCallback;
import org.springframework.retry.listener.MethodInvocationRetryListenerSupport;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
@Component(ImcCenterConstants.IMC_LISTENER)
@Slf4j
public class RestRetryImcListener extends MethodInvocationRetryListenerSupport implements RetryListener {
private RedisTemplate<String, Object> redisTemplate;
private DataCenterProperties properties;
@Autowired
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Autowired
public void setProperties(DataCenterProperties properties) {
this.properties = properties;
}
@Override
protected <T, E extends Throwable> boolean doOpen(RetryContext context, MethodInvocationRetryCallback<T, E> callback) {
String token = "";
try{
token = (String) redisTemplate.opsForValue().get(ImcCenterConstants.REDIS_TOKEN_KEY);
if (!StringUtils.hasLength(token)) {
token = refreshToken();
}
}catch (Exception e){
token = refreshToken();
}
RemoteUtils.TOKEN_CACHE.set(token);
return true;
}
@Override
protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
RemoteUtils.TOKEN_CACHE.remove();
}
@Override
protected <T, E extends Throwable> void doOnError(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
String methodName = callback.getInvocation().getMethod().getName();
log.error("执行方法[{}]出错,刷新令牌后重试,当前重试次数:{}",methodName, context.getRetryCount(),throwable);
String token = refreshToken();
RemoteUtils.TOKEN_CACHE.set(token);
}
private String refreshToken() {
Login body = new Login();
body.setUsername(properties.getUsername());
body.setPassword(RSAEncryptUtils.encrypt1(properties.getPassword(),properties.getPublicKey()));
Result<Object> result = RemoteUtils.post(MethodApi.GET_TOKEN, body);
if (!result.isSuccess()) {
log.info("刷新数据中心令牌失败:{}",result.getMessage());
return null;
}
JSONObject data = (JSONObject) JSON.toJSON(result.getResult());
String token = data.getString("token");
redisTemplate.opsForValue().set(ImcCenterConstants.REDIS_TOKEN_KEY,token, Duration.ofDays(1));
log.info("刷新影像中心令牌成功:{}", token);
return token;
}
}
@@ -0,0 +1,88 @@
package com.renkang.datacenter.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.renkang.datacenter.bean.Login;
import com.renkang.datacenter.util.RemoteUtils;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.util.RSAEncryptUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.interceptor.MethodInvocationRetryCallback;
import org.springframework.retry.listener.MethodInvocationRetryListenerSupport;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.Duration;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
@Component
@Slf4j
public class RestRetryListener extends MethodInvocationRetryListenerSupport implements RetryListener {
private RedisTemplate<String, Object> redisTemplate;
private DataCenterProperties properties;
@Autowired
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Autowired
public void setProperties(DataCenterProperties properties) {
this.properties = properties;
}
@Override
protected <T, E extends Throwable> boolean doOpen(RetryContext context, MethodInvocationRetryCallback<T, E> callback) {
String token = "";
try{
token = (String) redisTemplate.opsForValue().get(DataCenterConstants.REDIS_TOKEN_KEY);
if (!StringUtils.hasLength(token)) {
token = refreshToken();
}
}catch (Exception e){
token = refreshToken();
}
RemoteUtils.TOKEN_CACHE.set(token);
return true;
}
@Override
protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
RemoteUtils.TOKEN_CACHE.remove();
}
@Override
protected <T, E extends Throwable> void doOnError(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
String methodName = callback.getInvocation().getMethod().getName();
log.error("执行方法[{}]出错,刷新令牌后重试,当前重试次数:{}",methodName, context.getRetryCount(),throwable);
String token = refreshToken();
RemoteUtils.TOKEN_CACHE.set(token);
}
private String refreshToken() {
Login body = new Login();
body.setUsername(properties.getUsername());
body.setPassword(RSAEncryptUtils.encrypt1(properties.getPassword(),properties.getPublicKey()));
Result<Object> result = RemoteUtils.post(MethodApi.GET_TOKEN, body);
if (!result.isSuccess()) {
log.info("刷新数据中心令牌失败:{}",result.getMessage());
return null;
}
JSONObject data = (JSONObject) JSON.toJSON(result.getResult());
String token = data.getString("token");
redisTemplate.opsForValue().set(DataCenterConstants.REDIS_TOKEN_KEY,token, Duration.ofDays(1));
log.info("刷新数据中心令牌成功:{}", token);
return token;
}
}
@@ -0,0 +1,116 @@
package com.renkang.datacenter.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.renkang.datacenter.config.DataCenterConstants;
import com.renkang.datacenter.config.DataCenterProperties;
import com.renkang.datacenter.config.MethodApi;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.SpringContextUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* @author Shunzhi Jiang
* @since 2023/11/3
*/
public class RemoteUtils {
public static final ThreadLocal<String> TOKEN_CACHE = new ThreadLocal<>();
private static RestTemplate restTemplate;
private static DataCenterProperties properties;
public static RestTemplate getRestTemplate() {
if (Objects.isNull(restTemplate)) {
restTemplate = SpringContextUtils.getBean("dataCenterTemplate", RestTemplate.class);
}
return restTemplate;
}
public static DataCenterProperties getProperties() {
if (Objects.isNull(properties)) {
properties = SpringContextUtils.getBean(DataCenterProperties.class);
}
return properties;
}
public static Result<Object> get(MethodApi api, Map<String, Object> params) {
return exchange(api, HttpMethod.GET, null, params);
}
public static String downloadAsString(String filepath) {
return getRestTemplate().getForObject(urlHandlerForFileDown(filepath), String.class);
}
public static Result<Object> get(MethodApi api) {
return exchange(api, HttpMethod.GET, null, null);
}
public static Result<Object> post(MethodApi api, Object body) {
return exchange(api, HttpMethod.POST, body, null);
}
public static Result<Object> post(MethodApi api, Object body, Map<String, Object> params) {
return exchange(api, HttpMethod.POST, body, params);
}
public static Result<Object> post(MethodApi api, Map<String, Object> params) {
return exchange(api, HttpMethod.POST, null, params);
}
private static Result<Object> exchange(MethodApi api, HttpMethod method, Object body, Map<String, Object> params) {
HttpHeaders headers = new HttpHeaders();
String token = TOKEN_CACHE.get();
if (StringUtils.hasLength(token)) {
headers.add(DataCenterConstants.HEADER_TOKEN_KEY, TOKEN_CACHE.get());
}
HttpEntity<?> entity = new HttpEntity<>(body, headers);
if (CollectionUtils.isEmpty(params)) {
return responseHandler(getRestTemplate().exchange(urlHandler(api), method, entity, String.class));
} else {
return responseHandler(getRestTemplate().exchange(urlHandler(api), method, entity, String.class, params));
}
}
private static String urlHandler(MethodApi api) {
return urlHandler(api.getUrl());
}
private static String urlHandler(String url) {
String baseUrl = getProperties().getUrl();
String path = url;
if (!baseUrl.endsWith("/") && !path.startsWith("/")) {
path = "/".concat(path);
}
return baseUrl + path;
}
private static String urlHandlerForFileDown(String url) {
String fileUrl = "file/down/".concat(url).replace("//", "/");
return urlHandler(fileUrl);
}
private static Result<Object> responseHandler(ResponseEntity<String> entity) {
String body = entity.getBody();
JSONObject responseJson = JSON.parseObject(body);
String message = Optional.ofNullable(responseJson.getString("message")).orElse("");
if (responseJson.getBoolean("success")) {
return Result.OK(message, responseJson.get("result"));
} else {
return Result.error(message);
}
}
}
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>font-resources</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
+118
View File
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>monitoring-sync</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<!-- 打包名称 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>divide-package</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<start-class>com.renkang.sync.ApplicationMain</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,20 @@
package com.renkang.sync;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.stereotype.Repository;
/**
* @author Jiang Shunzhi
*/
@SpringBootApplication
@EnableJpaRepositories(includeFilters = @ComponentScan.Filter(classes = Repository.class))
public class ApplicationMain {
public static void main(String[] args) {
SpringApplication.run(ApplicationMain.class, args);
}
}
@@ -0,0 +1,135 @@
package com.renkang.sync.bean;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.io.Serializable;
/**
* 接口返回数据格式
*
* @author scott
* @email jeecgos@163.com
* @date 2019年1月19日
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标志
*/
private boolean success = true;
/**
* 返回处理消息
*/
private String message = "";
/**
* 返回代码
*/
private Integer code = 0;
/**
* 返回数据对象 data
*/
private T result;
/**
* 时间戳
*/
private long timestamp = System.currentTimeMillis();
@JsonIgnore
private String onlTable;
public Result() {
}
/**
* 兼容VUE3版token失效不跳转登录页面
*/
public Result(Integer code, String message) {
this.code = code;
this.message = message;
}
public static <T> Result<T> ok() {
Result<T> r = new Result<>();
r.setSuccess(true);
r.setCode(200);
return r;
}
/**
* 此方法是为了兼容升级所创建
*/
public static <T> Result<T> ok(String msg) {
Result<T> r = new Result<>();
r.setSuccess(true);
r.setCode(200);
r.setMessage(msg);
return r;
}
public static <T> Result<T> ok(T data) {
Result<T> r = new Result<>();
r.setSuccess(true);
r.setCode(200);
r.setResult(data);
return r;
}
public static <T> Result<T> ok(String msg, T data) {
Result<T> r = new Result<>();
r.setSuccess(true);
r.setCode(200);
r.setMessage(msg);
r.setResult(data);
return r;
}
public static <T> Result<T> error(String msg, T data) {
Result<T> r = new Result<>();
r.setSuccess(false);
r.setCode(500);
r.setMessage(msg);
r.setResult(data);
return r;
}
public static <T> Result<T> error(String msg) {
return error(500, msg);
}
public static <T> Result<T> error(int code, String msg) {
Result<T> r = new Result<>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
return r;
}
/**
* 无权限访问返回结果
*/
public static <T> Result<T> noAuth(String msg) {
return error(401, msg);
}
public Result<T> success(String message) {
this.message = message;
this.code = 200;
this.success = true;
return this;
}
public Result<T> error500(String message) {
this.message = message;
this.code = 500;
this.success = false;
return this;
}
}
@@ -0,0 +1,63 @@
package com.renkang.sync.bean.lefu;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.renkang.sync.entity.RemoteWeightManufacturerLefu;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Jiang Shunzhi
*/
@Data
public class BatchRecord {
@NotBlank(message = "Missing parameter: sn")
private String sn;
@NotBlank(message = "Missing parameter: type")
private String type;
@NotBlank(message = "Missing parameter: mac")
@Pattern(regexp = "([A-Fa-f0-9]{2}[:]){5}[A-Fa-f0-9]{2}", message = "MAC address does not meet the rules")
private String mac;
@NotBlank(message = "Missing parameter: charge")
private String charge;
private String firmwareVersion;
private String wifiVersion;
@Valid
private List<RecordData> data;
private RemoteWeightManufacturerLefu toRemoteEntity(RecordData data) {
RemoteWeightManufacturerLefu remoteWeightManufacturerLefu = new RemoteWeightManufacturerLefu();
remoteWeightManufacturerLefu.setDeviceSn(sn);
remoteWeightManufacturerLefu.setDeviceType(type);
remoteWeightManufacturerLefu.setDeviceMac(mac);
remoteWeightManufacturerLefu.setDeviceCharge(charge);
remoteWeightManufacturerLefu.setFirmwareVersion(firmwareVersion);
remoteWeightManufacturerLefu.setWifiVersion(wifiVersion);
remoteWeightManufacturerLefu.setDeviceImpedance(data.getImpedance());
remoteWeightManufacturerLefu.setDataTime(data.getDataTime());
remoteWeightManufacturerLefu.setWeight(data.getWeightNum());
remoteWeightManufacturerLefu.setHeartRate(data.getHeartRate());
remoteWeightManufacturerLefu.setCreateTime(DateUtil.date());
return remoteWeightManufacturerLefu;
}
public List<RemoteWeightManufacturerLefu> toRemoteEntities() {
return CollectionUtil.isEmpty(data) ? Collections.emptyList() : data.stream()
.map(this::toRemoteEntity)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,28 @@
package com.renkang.sync.bean.lefu;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author Jiang Shunzhi
*/
@Data
public class DeviceInfo {
private String sn;
private String mac;
private String firmwareVersion;
@JsonProperty("WifiVersion")
private String wifiVersion;
private String hardwareVersion;
private String charge;
private String type;
private Integer timezone;
}
@@ -0,0 +1,91 @@
package com.renkang.sync.bean.lefu;
import java.io.Serializable;
/**
* @author Jiang Shunzhi
*/
public class LefuResponse<T> implements Serializable {
private static final Integer CODE_OK = 0;
private static final Integer CODE_FAIL = 1;
private static final Integer CODE_SUCCESS = 200;
private Integer code;
private T data;
/**
* The firmware on the device has been successfully written; this parameter is for compatibility with existing devices.
*/
private Integer errorCode; // Registration success status, 0 for success, non-zero for failure
/**
* The firmware on the device has been successfully written; this parameter is for compatibility with existing devices.
*/
private String text; // Return message, which can explain the reason for success or the reason for failure
public LefuResponse() {
setCode(CODE_SUCCESS);
}
public static <T> LefuResponse<T> ok(String text) {
LefuResponse<T> scaleR = new LefuResponse<>();
scaleR.setErrorCode(CODE_OK);
scaleR.setText(text);
return scaleR;
}
public static <T> LefuResponse<T> ok(String text, T data) {
LefuResponse<T> scaleR = new LefuResponse<>();
scaleR.setErrorCode(CODE_OK);
scaleR.setText(text);
scaleR.setData(data);
return scaleR;
}
public static <T> LefuResponse<T> fail(String text, T data) {
LefuResponse<T> scaleR = new LefuResponse<>();
scaleR.setErrorCode(CODE_FAIL);
scaleR.setText(text);
scaleR.setData(data);
return scaleR;
}
public static <T> LefuResponse<T> fail(String text) {
LefuResponse<T> scaleR = new LefuResponse<>();
scaleR.setErrorCode(CODE_FAIL);
scaleR.setText(text);
return scaleR;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Integer getErrorCode() {
return errorCode;
}
public void setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
@@ -0,0 +1,54 @@
package com.renkang.sync.bean.lefu;
/**
* @author Jiang Shunzhi
*/
import cn.hutool.core.date.DateUtil;
import com.renkang.sync.entity.RemoteWeightManufacturerLefu;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class Record extends RecordData {
@NotBlank(message = "Missing parameter: sn")
private String sn;
@NotBlank(message = "Missing parameter: type")
private String type;
@NotBlank(message = "Missing parameter: mac")
@Pattern(regexp = "([A-Fa-f0-9]{2}[:]){5}[A-Fa-f0-9]{2}", message = "MAC address does not meet the rules")
private String mac;
@NotBlank(message = "Missing parameter: charge")
private String charge;
private String firmwareVersion;
private String wifiVersion;
public RemoteWeightManufacturerLefu toRemoteEntity() {
RemoteWeightManufacturerLefu remoteWeightManufacturerLefu = new RemoteWeightManufacturerLefu();
remoteWeightManufacturerLefu.setDeviceSn(sn);
remoteWeightManufacturerLefu.setDeviceType(type);
remoteWeightManufacturerLefu.setDeviceMac(mac);
remoteWeightManufacturerLefu.setDeviceCharge(charge);
remoteWeightManufacturerLefu.setFirmwareVersion(firmwareVersion);
remoteWeightManufacturerLefu.setWifiVersion(wifiVersion);
remoteWeightManufacturerLefu.setDeviceImpedance(getImpedance());
remoteWeightManufacturerLefu.setDataTime(getDataTime());
remoteWeightManufacturerLefu.setWeight(getWeightNum());
remoteWeightManufacturerLefu.setHeartRate(getHeartRate());
remoteWeightManufacturerLefu.setCreateTime(DateUtil.date());
return remoteWeightManufacturerLefu;
}
}
@@ -0,0 +1,35 @@
package com.renkang.sync.bean.lefu;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author Jiang Shunzhi
*/
@Data
public class RecordData {
private String weight;
private String impedance;
@NotBlank(message = "Missing parameter: timestamp")
private String timestamp;
private Integer heartRate;
public Date getDataTime() {
return StrUtil.isNumeric(getTimestamp()) ? DateUtil.date(Long.parseLong(getTimestamp())) : null;
}
public BigDecimal getWeightNum() {
return NumberUtil.isNumber(getWeight()) ? new BigDecimal(getWeight()) : null;
}
}
@@ -0,0 +1,15 @@
package com.renkang.sync.bean.lefu;
import lombok.Data;
/**
* @author Jiang Shunzhi
*/
@Data
public class TimeInfo {
private Long now;
private Integer unit;
}
@@ -0,0 +1,29 @@
package com.renkang.sync.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.Map;
/**
* @author Jiang Shunzhi
*/
@ConfigurationProperties("lefu")
@Data
public class LefuProperties {
private Map<String, Service> sync = new HashMap<>();
@Data
public static class Service {
private String baseUrl = "";
private String username = "";
private String password = "";
}
}
@@ -0,0 +1,48 @@
package com.renkang.sync.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
/**
* @author Jiang Shunzhi
*/
@Configuration
@EnableConfigurationProperties({SyncProperties.class, LefuProperties.class})
@ConditionalOnProperty(prefix = "spring.cloud.nacos.discovery", name = "enabled", havingValue = "false", matchIfMissing = true)
@EnableScheduling
@EnableAsync
public class SyncConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
@Bean
public TaskScheduler schedulingTaskExecutor() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(10);
threadPoolTaskScheduler.setThreadNamePrefix("task-pool-");
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
return threadPoolTaskScheduler;
}
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(10);
threadPoolTaskScheduler.setThreadNamePrefix("async-pool-");
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
return threadPoolTaskScheduler;
}
}
@@ -0,0 +1,22 @@
package com.renkang.sync.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* @author Jiang Shunzhi
*/
@ConfigurationProperties("sync")
@Data
public class SyncProperties {
private Integer size = 100;
private String cron = "0 */30 * * * *";
private List<String> baseUrls = new ArrayList<>();
}
@@ -0,0 +1,61 @@
package com.renkang.sync.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.renkang.sync.bean.lefu.*;
import com.renkang.sync.entity.RemoteWeightManufacturerLefu;
import com.renkang.sync.service.LefuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collections;
import java.util.List;
/**
* @author Jiang Shunzhi
*/
@RestController
@RequestMapping("/lefu/wifi")
public class LefuController {
@Autowired
private LefuService lefuService;
@PostMapping("/register")
public LefuResponse<TimeInfo> register(@Validated @RequestBody DeviceInfo deviceInfo) {
return LefuResponse.ok("DeviceInfo success", getTimeInfo());
}
private TimeInfo getTimeInfo() {
TimeInfo timeInfo = new TimeInfo();
timeInfo.setUnit(0);
timeInfo.setNow(System.currentTimeMillis());
return timeInfo;
}
@GetMapping("/config")
public LefuResponse<TimeInfo> config(@Validated @RequestBody DeviceInfo deviceInfo) {
return LefuResponse.ok("Get config info success", getTimeInfo());
}
@PostMapping("/record")
public LefuResponse<Boolean> record(@Validated @RequestBody Record record) throws JsonProcessingException {
RemoteWeightManufacturerLefu data = lefuService.record(record);
lefuService.sync(Collections.singletonList(data));
return LefuResponse.ok("success");
}
@PostMapping("/batchRecord")
public LefuResponse<Boolean> batchRecord(@Validated @RequestBody BatchRecord batchRecord) throws JsonProcessingException {
List<RemoteWeightManufacturerLefu> data = lefuService.batchRecord(batchRecord);
lefuService.sync(data);
return LefuResponse.ok("success");
}
@GetMapping("/list")
public LefuResponse<Page<RemoteWeightManufacturerLefu>> list(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {
return LefuResponse.ok("", lefuService.list(pageNo, pageSize));
}
}
@@ -0,0 +1,48 @@
package com.renkang.sync.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.renkang.sync.bean.Result;
import com.renkang.sync.entity.IdAware;
import com.renkang.sync.service.DataService;
import com.renkang.sync.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
import java.math.BigInteger;
import java.util.Objects;
import static com.renkang.sync.util.Constants.GET_MAX_ID;
import static com.renkang.sync.util.Constants.SAVE_DATA;
/**
* @Name SyncController
* @Author YangYuanChen
* @Data 2024/7/26下午4:22
*/
@RestController
@ConditionalOnProperty(prefix = "spring.cloud.nacos.discovery", name = "enabled", havingValue = "true")
public class SyncController {
private DataService dataService;
@Autowired
public void setDataService(DataService dataService) {
this.dataService = dataService;
}
@GetMapping(GET_MAX_ID)
public Result<String> getAllDataId(@RequestParam int type) {
BigInteger maxId = dataService.getMaxId(Constants.getClass(type));
return Result.ok("", Objects.nonNull(maxId) ? maxId.toString() : "0");
}
@PostMapping(SAVE_DATA)
public Result<Void> saveData(@RequestParam int type, @RequestBody String json) throws JsonProcessingException {
Class<? extends IdAware> clz = Constants.getClass(type);
Objects.requireNonNull(clz, "No type match.");
dataService.saveData(clz, json);
return Result.ok();
}
}
@@ -0,0 +1,12 @@
package com.renkang.sync.entity;
import java.math.BigInteger;
/**
* @author Jiang Shunzhi
*/
public interface IdAware {
BigInteger getId();
}
@@ -0,0 +1,73 @@
package com.renkang.sync.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
/**
* @Name IndoorEnvironmentData
* @Author YangYuanChen
* @Data 20242024/7/25下午2:02
*/
@Entity
@Table(name = "indoor_environment_data")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class IndoorEnvironmentData implements IdAware, Serializable {
@Id
@Column(name = "id")
private BigInteger id;
@Column(name = "meter_code")
private String meterCode;
@Column(name = "read_time")
private Integer readTime;
@Column(name = "insert_time")
private Integer insertTime;
@Column(name = "temperature")
private BigDecimal temperature;
@Column(name = "humidity")
private BigDecimal humidity;
@Column(name = "carbon_dioxide")
private BigDecimal carbonDioxide;
@Column(name = "pm25")
private BigDecimal pm25;
@Column(name = "pm10")
private BigDecimal pm10;
@Column(name = "hcho")
private BigDecimal hcho;
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) {
return false;
}
IndoorEnvironmentData that = (IndoorEnvironmentData) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
@@ -0,0 +1,77 @@
package com.renkang.sync.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
/**
* @Name OutdoorEnvironmentData
* @Author YangYuanChen
* @Data 20242024/7/25下午2:05
*/
@Entity
@Table(name = "outdoor_environment_data")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class OutdoorEnvironmentData implements IdAware, Serializable {
@Id
@Column(name = "id")
private BigInteger id;
@Column(name = "meter_code")
private String meterCode;
@Column(name = "read_time")
private Integer readTime;
@Column(name = "insert_time")
private Integer insertTime;
@Column(name = "temperature")
private BigDecimal temperature;
@Column(name = "humidity")
private BigDecimal humidity;
@Column(name = "pm25")
private BigDecimal pm25;
@Column(name = "pm10")
private BigDecimal pm10;
@Column(name = "illuminance")
private BigDecimal illuminance;
@Column(name = "wind_speed")
private BigDecimal windSpeed;
@Column(name = "wind_direction")
private Integer windDirection;
@Column(name = "rs_ra")
private BigDecimal rsRa;
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) {
return false;
}
OutdoorEnvironmentData that = (OutdoorEnvironmentData) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
@@ -0,0 +1,77 @@
package com.renkang.sync.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
/**
* @Name PollenData
* @Author YangYuanChen
* @Data 20242024/7/25下午1:46
*/
@Entity
@Table(name = "pollen_data")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class PollenData implements IdAware, Serializable {
@Id
@Column(name = "id")
private BigInteger id;
@Column(name = "meter_code")
private String meterCode;
@Column(name = "read_time")
private Integer readTime;
@Column(name = "insert_time")
private Integer insertTime;
@Column(name = "one")
private Integer one;
@Column(name = "two")
private Integer two;
@Column(name = "three")
private Integer three;
@Column(name = "four")
private Integer four;
@Column(name = "five")
private Integer five;
@Column(name = "six")
private Integer six;
@Column(name = "temperature")
private BigDecimal temperature;
@Column(name = "humidity")
private BigDecimal humidity;
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) {
return false;
}
PollenData that = (PollenData) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
@@ -0,0 +1,65 @@
package com.renkang.sync.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author admin
*/
@Data
@Entity
@Table(name = "remote_weight_manufacturer_lefu")
public class RemoteWeightManufacturerLefu {
@Id
@Size(max = 32)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "snow_flake_generator")
@GenericGenerator(name = "snow_flake_generator", strategy = "com.renkang.sync.util.SnowFlakeIdGenerator")
@Column(name = "id", nullable = false, length = 32)
private String id;
@Size(max = 32)
@Column(name = "device_sn", length = 32)
private String deviceSn;
@Size(max = 32)
@Column(name = "device_type", length = 32)
private String deviceType;
@Size(max = 32)
@Column(name = "device_mac", length = 32)
private String deviceMac;
@Size(max = 32)
@Column(name = "device_charge", length = 32)
private String deviceCharge;
@Size(max = 32)
@Column(name = "device_impedance", length = 32)
private String deviceImpedance;
@Size(max = 32)
@Column(name = "wifi_version", length = 32)
private String wifiVersion;
@Size(max = 32)
@Column(name = "firmware_version", length = 32)
private String firmwareVersion;
@Column(name = "data_time")
private Date dataTime;
@Column(name = "weight", precision = 6, scale = 2)
private BigDecimal weight;
@Column(name = "heart_rate")
private Integer heartRate;
@Column(name = "create_time")
private Date createTime;
}
@@ -0,0 +1,77 @@
package com.renkang.sync.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
/**
* @Name WaterQualityData
* @Author YangYuanChen
* @Data 20242024/7/25下午2:41
*/
@Entity
@Table(name = "water_quality_data")
@Getter
@Setter
@ToString
@RequiredArgsConstructor
public class WaterQualityData implements IdAware, Serializable {
@Id
@Column(name = "id")
private BigInteger id;
@Column(name = "meter_code")
private String meterCode;
@Column(name = "insert_time")
private Integer insertTime;
@Column(name = "read_time")
private Integer readTime;
@Column(name = "temperature")
private BigDecimal temperature;
@Column(name = "conductivity")
private BigDecimal conductivity;
@Column(name = "turbidity")
private BigDecimal turbidity;
@Column(name = "residual_chlorine")
private BigDecimal residualChlorine;
@Column(name = "ph")
private BigDecimal ph;
@Column(name = "salinity")
private BigDecimal salinity;
@Column(name = "resistivity")
private BigDecimal resistivity;
@Column(name = "tds")
private BigDecimal tds;
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) {
return false;
}
WaterQualityData that = (WaterQualityData) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
@@ -0,0 +1,31 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.IdAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.math.BigInteger;
/**
* @author Jiang Shunzhi
*/
public interface BaseRepository<T extends IdAware> extends JpaRepository<T, BigInteger> {
/**
* 根据起始ID查询分页数据
*
* @param id 起始ID
* @param pageable 分页参数
* @return 分页数据
*/
Page<T> findByIdGreaterThan(BigInteger id, Pageable pageable);
/**
* 查询所有数据并返回 ID 最大的 BigInteger
*
* @return 最大ID值
*/
BigInteger findMaxId();
}
@@ -0,0 +1,26 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.IndoorEnvironmentData;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
/**
* @Name IndoorRepository
* @Author YangYuanChen
* @Data 20242024/7/25下午4:52
*/
@Repository
public interface IndoorRepository extends BaseRepository<IndoorEnvironmentData> {
/**
* 查询所有数据并返回 ID 最大的 BigInteger
*
* @return 最大ID值
*/
@Override
@Query("SELECT MAX(id) FROM IndoorEnvironmentData")
BigInteger findMaxId();
}
@@ -0,0 +1,12 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.RemoteWeightManufacturerLefu;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author Jiang Shunzhi
*/
@Repository
public interface LefuRepository extends JpaRepository<RemoteWeightManufacturerLefu, String> {
}
@@ -0,0 +1,26 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.OutdoorEnvironmentData;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
/**
* @Name OutdoorRepository
* @Author YangYuanChen
* @Data 20242024/7/25下午4:57
*/
@Repository
public interface OutdoorRepository extends BaseRepository<OutdoorEnvironmentData> {
/**
* 查询所有数据并返回 ID 最大的 BigInteger
*
* @return 最大ID值
*/
@Override
@Query("SELECT MAX(id) FROM OutdoorEnvironmentData")
BigInteger findMaxId();
}
@@ -0,0 +1,26 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.PollenData;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
/**
* @Name PollenRepository
* @Author YangYuanChen
* @Data 20242024/7/25下午4:58
*/
@Repository
public interface PollenRepository extends BaseRepository<PollenData> {
/**
* 查询所有数据并返回 ID 最大的 BigInteger
*
* @return 最大ID值
*/
@Override
@Query("SELECT MAX(id) FROM PollenData")
BigInteger findMaxId();
}
@@ -0,0 +1,26 @@
package com.renkang.sync.repository;
import com.renkang.sync.entity.WaterQualityData;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
/**
* @Name WaterRepository
* @Author YangYuanChen
* @Data 20242024/7/25下午4:59
*/
@Repository
public interface WaterRepository extends BaseRepository<WaterQualityData> {
/**
* 查询所有数据并返回 ID 最大的 BigInteger
*
* @return 最大ID值
*/
@Override
@Query("SELECT MAX(id) FROM WaterQualityData")
BigInteger findMaxId();
}
@@ -0,0 +1,123 @@
package com.renkang.sync.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.renkang.sync.entity.*;
import com.renkang.sync.repository.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
/**
* @Name DataService
* @Author YangYuanChen
* @Data 20242024/7/26下午3:41
*/
@Service
@Slf4j
public class DataService {
private IndoorRepository indoorRepository;
private OutdoorRepository outdoorRepository;
private PollenRepository pollenRepository;
private WaterRepository waterRepository;
private ObjectMapper objectMapper;
@Autowired
public void setIndoorRepository(IndoorRepository indoorRepository) {
this.indoorRepository = indoorRepository;
}
@Autowired
public void setOutdoorRepository(OutdoorRepository outdoorRepository) {
this.outdoorRepository = outdoorRepository;
}
@Autowired
public void setPollenRepository(PollenRepository pollenRepository) {
this.pollenRepository = pollenRepository;
}
@Autowired
public void setWaterRepository(WaterRepository waterRepository) {
this.waterRepository = waterRepository;
}
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@SuppressWarnings("unchecked")
public <T extends IdAware> Page<T> getPagedDate(Class<T> clz, BigInteger start, Pageable pageable) {
if (clz.equals(IndoorEnvironmentData.class)) {
return (Page<T>) indoorRepository.findByIdGreaterThan(start, pageable);
} else if (clz.equals(OutdoorEnvironmentData.class)) {
return (Page<T>) outdoorRepository.findByIdGreaterThan(start, pageable);
} else if (clz.equals(PollenData.class)) {
return (Page<T>) pollenRepository.findByIdGreaterThan(start, pageable);
} else if (clz.equals(WaterQualityData.class)) {
return (Page<T>) waterRepository.findByIdGreaterThan(start, pageable);
}
return null;
}
public void saveData(Class<? extends IdAware> clz, String json) throws JsonProcessingException {
if (clz.equals(IndoorEnvironmentData.class)) {
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, IndoorEnvironmentData.class);
List<IndoorEnvironmentData> data = objectMapper.readValue(json, javaType);
save(indoorRepository, data);
} else if (clz.equals(OutdoorEnvironmentData.class)) {
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, OutdoorEnvironmentData.class);
List<OutdoorEnvironmentData> data = objectMapper.readValue(json, javaType);
save(outdoorRepository, data);
} else if (clz.equals(PollenData.class)) {
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, PollenData.class);
List<PollenData> data = objectMapper.readValue(json, javaType);
save(pollenRepository, data);
} else if (clz.equals(WaterQualityData.class)) {
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, WaterQualityData.class);
List<WaterQualityData> data = objectMapper.readValue(json, javaType);
save(waterRepository, data);
}
}
private <T extends IdAware> void save(BaseRepository<T> repository, List<T> data) {
repository.saveAll(data);
}
public BigInteger getMaxId(Class<?> clz) {
BaseRepository<? extends IdAware> repository = getRepository(clz);
Objects.requireNonNull(repository, "No repository match.");
return repository.findMaxId();
}
private BaseRepository<? extends IdAware> getRepository(Class<?> clz) {
if (clz == null) {
return null;
}
if (clz.equals(IndoorEnvironmentData.class)) {
return indoorRepository;
} else if (clz.equals(OutdoorEnvironmentData.class)) {
return outdoorRepository;
} else if (clz.equals(PollenData.class)) {
return pollenRepository;
} else if (clz.equals(WaterQualityData.class)) {
return waterRepository;
} else {
return null;
}
}
}
@@ -0,0 +1,186 @@
package com.renkang.sync.service;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.renkang.sync.bean.Result;
import com.renkang.sync.bean.lefu.BatchRecord;
import com.renkang.sync.bean.lefu.Record;
import com.renkang.sync.config.LefuProperties;
import com.renkang.sync.entity.RemoteWeightManufacturerLefu;
import com.renkang.sync.repository.LefuRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Jiang Shunzhi
*/
@Service
@Slf4j
public class LefuService {
public static final String X_ACCESS_TOKEN = "X-Access-Token";
public static String PUSH_DATA = "/remote/data/device/weighing/lefu/sync";
public static String CHECK_TOKEN = "/sys/checkToken";
public static String GET_TOKEN = "/sys/thirdLogin";
private final Map<String, String> tokenMap = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper;
private LefuRepository lefuRepository;
private LefuProperties lefuProperties;
private RestTemplate restTemplate;
public LefuService() {
objectMapper = new ObjectMapper();
//处理bigDecimal
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
//处理失败
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false);
//默认的处理日期时间格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
objectMapper.registerModule(javaTimeModule);
}
@Autowired
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Autowired
public void setLefuProperties(LefuProperties lefuProperties) {
this.lefuProperties = lefuProperties;
}
@Autowired
public void setLefuRepository(LefuRepository lefuRepository) {
this.lefuRepository = lefuRepository;
}
@Async
public void sync(List<RemoteWeightManufacturerLefu> data) throws JsonProcessingException {
Map<String, LefuProperties.Service> sync = lefuProperties.getSync();
for (Map.Entry<String, LefuProperties.Service> entry : sync.entrySet()) {
String name = entry.getKey();
LefuProperties.Service service = entry.getValue();
String url = service.getBaseUrl() + PUSH_DATA;
ResponseEntity<Result<Void>> response = restTemplate.exchange(
url,
HttpMethod.POST,
new HttpEntity<>(objectMapper.writeValueAsString(data), getHeaders(name, service)),
new ParameterizedTypeReference<Result<Void>>() {
});
if (!response.getStatusCode().equals(HttpStatus.OK)
|| ObjectUtils.isEmpty(response.getBody())
|| !response.getBody().isSuccess()) {
throw new RuntimeException("Push Lefu Data Error remotely");
}
log.info("Push Lefu Success {} for {}", data.size(), name);
}
}
private HttpHeaders getHeaders(String name, LefuProperties.Service service) throws JsonProcessingException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(X_ACCESS_TOKEN, getToken(name, service));
return httpHeaders;
}
private String getToken(String name, LefuProperties.Service service) throws JsonProcessingException {
String token = tokenMap.get(name);
if (StrUtil.isBlank(token)) {
remoteToken(name, service);
} else {
if (!checkToken(token, service)) {
remoteToken(name, service);
}
}
return tokenMap.get(name);
}
private void remoteToken(String name, LefuProperties.Service service) throws JsonProcessingException {
Map<String, String> data = new HashMap<>();
data.put("username", service.getUsername());
data.put("password", service.getPassword());
String url = service.getBaseUrl() + GET_TOKEN;
ResponseEntity<Result<Map<String, String>>> response = restTemplate.exchange(
url,
HttpMethod.POST,
new HttpEntity<>(objectMapper.writeValueAsString(data)),
new ParameterizedTypeReference<Result<Map<String, String>>>() {
});
if (!response.getStatusCode().equals(HttpStatus.OK)
|| ObjectUtils.isEmpty(response.getBody())
|| !response.getBody().isSuccess()) {
throw new RuntimeException("Get Token Error remotely");
}
String token = response.getBody().getResult().get("token");
tokenMap.put(name, token);
}
private boolean checkToken(String token, LefuProperties.Service service) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(X_ACCESS_TOKEN, token);
String url = service.getBaseUrl() + CHECK_TOKEN;
ResponseEntity<Result<Void>> response = restTemplate.exchange(
url,
HttpMethod.POST,
new HttpEntity<>(httpHeaders),
new ParameterizedTypeReference<Result<Void>>() {
});
return response.getStatusCode().equals(HttpStatus.OK)
&& !ObjectUtils.isEmpty(response.getBody())
&& response.getBody().isSuccess();
}
public RemoteWeightManufacturerLefu record(Record record) {
return lefuRepository.save(record.toRemoteEntity());
}
public List<RemoteWeightManufacturerLefu> batchRecord(BatchRecord batchRecord) {
return lefuRepository.saveAll(batchRecord.toRemoteEntities());
}
public Page<RemoteWeightManufacturerLefu> list(Integer pageNo, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNo, pageSize).withSort(Sort.by(Sort.Order.desc("createTime")));
return lefuRepository.findAll(pageable);
}
}
@@ -0,0 +1,212 @@
package com.renkang.sync.task;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.renkang.sync.bean.Result;
import com.renkang.sync.config.SyncProperties;
import com.renkang.sync.entity.*;
import com.renkang.sync.service.DataService;
import com.renkang.sync.util.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.util.List;
import static com.renkang.sync.util.Constants.*;
/**
* @Name SyncTask
* @Author YangYuanChen
* @Data 20242024/7/29下午7:45
*/
@Component
@Slf4j
@ConditionalOnProperty(prefix = "spring.cloud.nacos.discovery", name = "enabled", havingValue = "false", matchIfMissing = true)
public class SyncTask {
private static final int[] TYPES = new int[]{
INDOOR,
OUTDOOR,
POLLEN,
WATER
};
private DataService dataService;
private SyncProperties syncProperties;
private RestTemplate restTemplate;
private TaskScheduler schedulingTaskExecutor;
private ObjectMapper objectMapper;
@Autowired
public void setDataService(DataService dataService) {
this.dataService = dataService;
}
@Autowired
public void setSyncProperties(SyncProperties syncProperties) {
this.syncProperties = syncProperties;
}
@Autowired
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Autowired
public void setSchedulingTaskExecutor(TaskScheduler schedulingTaskExecutor) {
this.schedulingTaskExecutor = schedulingTaskExecutor;
}
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@PostConstruct
public void init() {
String cron = syncProperties.getCron();
CronTrigger trigger = new CronTrigger(cron);
for (String baseUrl : syncProperties.getBaseUrls()) {
log.info("Add schedule task for base: {}", baseUrl);
schedulingTaskExecutor.schedule(() -> {
try {
run(baseUrl);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}, trigger);
}
}
private void run(String baseUrl) throws JsonProcessingException {
for (int type : TYPES) {
try {
BigInteger remoteMaxId = getRemoteMaxId(baseUrl, type);
BigInteger localMaxId = getLocalMaxId(type);
log.info("MaxId for base {} is: {}", baseUrl, remoteMaxId);
if (remoteMaxId.compareTo(localMaxId) >= 0) {
log.info("remote base {} is up-to-date", baseUrl);
continue;
}
pushData(baseUrl, remoteMaxId, type);
} catch (Exception e) {
log.error("Error has occurred for base {} and type {}", baseUrl, type, e);
}
}
}
private BigInteger getRemoteMaxId(String baseUrl, int type) {
String url = getUrl(baseUrl, type, GET_MAX_ID);
ResponseEntity<Result<String>> response = restTemplate.exchange(
url,
HttpMethod.GET,
HttpEntity.EMPTY,
new ParameterizedTypeReference<Result<String>>() {
});
if (!response.getStatusCode().equals(HttpStatus.OK)
|| ObjectUtils.isEmpty(response.getBody())
|| !StringUtils.hasText(response.getBody().getResult())) {
errorLog(response);
throw new RuntimeException("Get maxId Error remotely");
}
return new BigInteger(response.getBody().getResult());
}
private BigInteger getLocalMaxId(int type) {
BigInteger maxId = dataService.getMaxId(Constants.getClass(type));
if (ObjectUtils.isEmpty(maxId)) {
throw new RuntimeException("Get maxId Error locally");
}
return maxId;
}
private void pushData(String baseUrl, BigInteger start, int type) throws JsonProcessingException {
Pageable pageable = pageableInstance();
Page<?> pagedData;
boolean first = true;
do {
if (first) {
first = false;
} else {
pageable = pageable.next();
}
pagedData = getPagedData(start, type, pageable);
if (pagedData.isEmpty()) {
return;
}
remotePush(baseUrl, pagedData.getContent(), type);
} while (pagedData.hasNext());
}
private Pageable pageableInstance() {
Sort sort = Sort.by(Sort.Order.asc(ID_FIELD));
return PageRequest.of(0, syncProperties.getSize(), sort);
}
private <T extends IdAware> Page<T> getPagedData(BigInteger start, Class<T> clz, Pageable pageable) {
return dataService.getPagedDate(clz, start, pageable);
}
private Page<?> getPagedData(BigInteger start, int type, Pageable pageable) {
if (type == INDOOR) {
return getPagedData(start, IndoorEnvironmentData.class, pageable);
} else if (type == OUTDOOR) {
return getPagedData(start, OutdoorEnvironmentData.class, pageable);
} else if (type == POLLEN) {
return getPagedData(start, PollenData.class, pageable);
} else if (type == WATER) {
return getPagedData(start, WaterQualityData.class, pageable);
} else {
throw new RuntimeException("Unknown type");
}
}
private void remotePush(String baseUrl, List<?> data, int type) throws JsonProcessingException {
String url = getUrl(baseUrl, type, SAVE_DATA);
ResponseEntity<Result<Void>> response = restTemplate.exchange(
url,
HttpMethod.POST,
new HttpEntity<>(objectMapper.writeValueAsString(data)),
new ParameterizedTypeReference<Result<Void>>() {
});
if (!response.getStatusCode().equals(HttpStatus.OK)
|| ObjectUtils.isEmpty(response.getBody())
|| !response.getBody().isSuccess()) {
errorLog(response);
throw new RuntimeException("Push Data Error remotely");
}
log.info("Push Success {} for type {}", data.size(), type);
}
private void errorLog(ResponseEntity<?> response) {
log.error("MaxId HttpCode: {}", response.getStatusCodeValue());
log.error("MaxId Response: {}", response.getBody());
}
private String getUrl(String baseUrl, int type, String api) {
return UriComponentsBuilder.fromHttpUrl(baseUrl + api)
.queryParam(TYPE_PARAM, type)
.encode()
.toUriString();
}
}
@@ -0,0 +1,46 @@
package com.renkang.sync.util;
import com.renkang.sync.entity.*;
/**
* @author Jiang Shunzhi
*/
public interface Constants {
int INDOOR = 1;
int OUTDOOR = 2;
int POLLEN = 3;
int WATER = 4;
String TYPE_PARAM = "type";
String ID_FIELD = "id";
String GET_MAX_ID = "/maxId";
String SAVE_DATA = "/saveData";
/**
* 根据字典类型返回对应的类
*
* @param type 类型
* @return 实体类
*/
static Class<? extends IdAware> getClass(int type) {
switch (type) {
case INDOOR:
return IndoorEnvironmentData.class;
case OUTDOOR:
return OutdoorEnvironmentData.class;
case POLLEN:
return PollenData.class;
case WATER:
return WaterQualityData.class;
default:
return null;
}
}
}
@@ -0,0 +1,54 @@
package com.renkang.sync.util;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type;
import java.io.Serializable;
import java.util.Properties;
/**
* @author Jiang Shunzhi
*/
@Slf4j
public class SnowFlakeIdGenerator implements IdentifierGenerator {
private final Snowflake snowFlake;
private Class<?> type;
public SnowFlakeIdGenerator() {
this.snowFlake = IdUtil.getSnowflake();
}
public synchronized long snowflakeId() {
return snowFlake.nextId();
}
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object)
throws HibernateException {
long id = snowflakeId();
if (Long.class.isAssignableFrom(type)) {
return id;
} else if (String.class.isAssignableFrom(type)) {
return Long.toString(id);
} else if (byte[].class.isAssignableFrom(type)) {
return Long.toUnsignedString(id).getBytes();
} else {
throw new HibernateException("Unanticipated return type [" + type.getName() + "] for ID conversion");
}
}
@Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
this.type = type.getReturnedClass();
}
}
@@ -0,0 +1,8 @@
PROFILE_NAME=dev
SERVER_PORT=27002
NACOS_SERVER_ADDR=nacos.yg.dt.io:80
NACOS_USERNAME=cqyt
NACOS_PASSWORD=Aa123456
NACOS_NAMESPACE=2639a2d4-7b64-4408-98c9-b97e0bdf4c1f
NACOS_GROUP=dev
NACOS_ENABLE=false
@@ -0,0 +1,27 @@
server:
port: ${SERVER_PORT:27001}
spring:
application:
name: env-sync
config:
import:
- optional:nacos:${spring.application.name}-${PROFILE_NAME}.yaml
cloud:
nacos:
server-addr: ${NACOS_SERVER_ADDR}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
config:
enabled: true
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
discovery:
enabled: ${NACOS_ENABLE:false}
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-parent</artifactId>
<version>4.0</version>
</parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
<packaging>pom</packaging>
<modules>
<module>renkang-starter-batch</module>
<module>renkang-starter-aspose-word</module>
<module>data-center-api</module>
<module>font-resources</module>
<module>monitoring-sync</module>
<module>renkang-test</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>renkang-starter-aspose-word</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,88 @@
package com.renkang.aspose.configure;
import com.aspose.words.*;
import com.renkang.aspose.util.DocConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Objects;
import java.util.stream.Stream;
/**
* @author Shunzhi Jiang
* @since 2023/11/29
*/
@AutoConfiguration
@EnableConfigurationProperties(AsposeWordProperties.class)
@Slf4j
public class AsposeAutoConfiguration {
private final AsposeWordProperties properties;
public AsposeAutoConfiguration(AsposeWordProperties properties) {
this.properties = properties;
}
@Bean
public License license() throws Exception {
License license = new License();
ClassPathResource resource = new ClassPathResource("license.xml");
license.setLicense(resource.getInputStream());
log.info("加载Aspose License成功");
return license;
}
@Bean
public LoadOptions loadOptions() throws IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(properties.getFontLocation());
FontSourceBase[] fonts = Stream.of(resources)
.map(resource -> {
try {
if (!isFont(resource)) {
return null;
}
log.info("加载字体{}", resource.getFilename());
byte[] bytes = StreamUtils.copyToByteArray(resource.getInputStream());
return new MemoryFontSource(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.filter(Objects::nonNull)
.toArray(FontSourceBase[]::new);
LoadOptions loadOptions = new LoadOptions();
FontSettings fontSettings = new FontSettings();
fontSettings.setFontsSources(fonts);
loadOptions.setFontSettings(fontSettings);
DocConvertUtil.setLoadOptions(loadOptions);
return loadOptions;
}
private boolean isFont(Resource resource) {
String filename = resource.getFilename();
if (!StringUtils.hasText(filename)) {
return false;
}
int i = filename.lastIndexOf(".");
if (i < 0) {
return false;
}
String ext = filename.substring(i + 1);
return "ttf".equalsIgnoreCase(ext)
|| "ttc".equalsIgnoreCase(ext)
|| "woff".equalsIgnoreCase(ext)
|| "woff2".equalsIgnoreCase(ext)
|| "oft".equalsIgnoreCase(ext);
}
}
@@ -0,0 +1,16 @@
package com.renkang.aspose.configure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Shunzhi Jiang
* @since 2023/11/29
*/
@ConfigurationProperties("renkang.aspose")
@Data
public class AsposeWordProperties {
private String fontLocation = "classpath*:font/**/*";
}
@@ -0,0 +1,63 @@
package com.renkang.aspose.util;
import com.aspose.words.Document;
import com.aspose.words.LoadOptions;
import com.aspose.words.SaveFormat;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Shunzhi Jiang
* @since 2023/11/29
*/
public class DocConvertUtil {
private static LoadOptions loadOptions;
public static void setLoadOptions(LoadOptions loadOptions) {
DocConvertUtil.loadOptions = loadOptions;
}
public static boolean checkTraversal(String path) {
return path.contains("..");
}
public static void docxConvertPdfSaveLocalAspose(String docxPath, String pdfPath) throws Exception {
if (checkTraversal(docxPath)) {
throw new InvalidPathException(docxPath, "Illegal path symbol");
}
if (checkTraversal(pdfPath)) {
throw new InvalidPathException(pdfPath, "Illegal path symbol");
}
//保存pdf
//保存pdf
try (
InputStream ins = Files.newInputStream(Paths.get(docxPath));
OutputStream outs = Files.newOutputStream(Paths.get(pdfPath));
) {
Document doc = new Document(ins, loadOptions);
doc.save(outs, SaveFormat.PDF);
//关流
outs.flush();
}
}
public static void docxConvertPdfSaveLocalAspose(Path docxPath, Path pdfPath) throws Exception {
//保存pdf
try (
InputStream ins = Files.newInputStream(docxPath);
OutputStream outs = Files.newOutputStream(pdfPath)
) {
Document doc = new Document(ins, loadOptions);
doc.save(outs, SaveFormat.PDF);
//关流
outs.flush();
}
}
}
@@ -0,0 +1,15 @@
<License>
<Data>
<Products>
<Product>Aspose.Total for Java</Product>
<Product>Aspose.Words for Java</Product>
</Products>
<EditionType>Enterprise</EditionType>
<SubscriptionExpiry>20991231</SubscriptionExpiry>
<LicenseExpiry>20991231</LicenseExpiry>
<SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
</Data>
<Signature>
sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=
</Signature>
</License>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>renkang-starter-batch</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,132 @@
package com.renkang.batch.configure;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.batch.BatchDataSource;
import org.springframework.boot.autoconfigure.batch.BatchDataSourceScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.List;
/**
* @author Shunzhi Jiang
* @since 2023/11/9
*/
@AutoConfiguration(
before = {
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
DynamicDataSourceAutoConfiguration.class
}
)
@EnableConfigurationProperties({RenkangBatchProperties.class, BatchProperties.class})
@EnableBatchProcessing
public class BatchAutoConfiguration {
private final BatchProperties batchProperties;
private final RenkangBatchProperties renkangBatchProperties;
private final DynamicDataSourceProperties dynamicDataSourceProperties;
public BatchAutoConfiguration(
BatchProperties batchProperties,
RenkangBatchProperties renkangBatchProperties,
DynamicDataSourceProperties dynamicDataSourceProperties
) {
this.batchProperties = batchProperties;
this.renkangBatchProperties = renkangBatchProperties;
this.dynamicDataSourceProperties = dynamicDataSourceProperties;
}
@Bean
@Primary
public PlatformTransactionManager bizTransactionManager(DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) {
JdbcTransactionManager bizTransactionManager = new JdbcTransactionManager(dataSource);
transactionManagerCustomizers.customize(bizTransactionManager);
return bizTransactionManager;
}
@Bean
public BatchConfigurer batchConfigurer(
@BatchDataSource DataSource dataSource,
TransactionManagerCustomizers transactionManagerCustomizers,
@Qualifier("batchTaskExecutor") TaskExecutor taskExecutor) {
return new CustomBatchConfigurer(batchProperties, dataSource, transactionManagerCustomizers, taskExecutor);
}
@Bean("batchTaskExecutor")
@ConditionalOnProperty(prefix = "renkang.batch", name = "async", havingValue = "true", matchIfMissing = false)
public TaskExecutor asyncBatchTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(16);
taskExecutor.setThreadNamePrefix("Job-Pool-");
taskExecutor.setMaxPoolSize(32);
taskExecutor.setQueueCapacity(16);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.afterPropertiesSet();
return taskExecutor;
}
@Bean("batchTaskExecutor")
@ConditionalOnProperty(prefix = "renkang.batch", name = "async", havingValue = "false", matchIfMissing = true)
public TaskExecutor syncBatchTaskExecutor() {
return new SyncTaskExecutor();
}
@Bean
@Primary
public DataSource dataSource(List<DynamicDataSourceProvider> providers) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(providers);
dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
dataSource.setStrict(dynamicDataSourceProperties.getStrict());
dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
dataSource.setSeata(dynamicDataSourceProperties.getSeata());
return dataSource;
}
@Bean
@ConditionalOnProperty(prefix = "renkang.batch", name = "jdbc-url")
@BatchDataSource
public DataSource batchDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(renkangBatchProperties.getJdbcUrl());
config.setUsername(renkangBatchProperties.getUsername());
config.setPassword(renkangBatchProperties.getPassword());
config.setDriverClassName(renkangBatchProperties.getDriverClassName());
return new HikariDataSource(config);
}
@Bean
public BatchDataSourceScriptDatabaseInitializer batchDataSourceInitializer(
DataSource dataSource,
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
BatchProperties properties
) {
return new BatchDataSourceScriptDatabaseInitializer(batchDataSource.getIfAvailable(() -> dataSource),
properties.getJdbc());
}
}
@@ -0,0 +1,47 @@
package com.renkang.batch.configure;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer;
import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.core.task.TaskExecutor;
import javax.sql.DataSource;
/**
* @author Shunzhi Jiang
* @since 2023/11/9
*/
public class CustomBatchConfigurer extends BasicBatchConfigurer {
private TaskExecutor taskExecutor;
/**
* Create a new {@link BasicBatchConfigurer} instance.
*
* @param properties the batch properties
* @param dataSource the underlying data source
* @param transactionManagerCustomizers transaction manager customizers (or
* {@code null})
*/
protected CustomBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers) {
super(properties, dataSource, transactionManagerCustomizers);
}
public CustomBatchConfigurer(BatchProperties properties, DataSource dataSource, TransactionManagerCustomizers transactionManagerCustomizers, TaskExecutor taskExecutor) {
super(properties, dataSource, transactionManagerCustomizers);
this.taskExecutor = taskExecutor;
}
@Override
protected JobLauncher createJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
if (taskExecutor != null) {
jobLauncher.setTaskExecutor(taskExecutor);
}
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
}
@@ -0,0 +1,26 @@
package com.renkang.batch.configure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Shunzhi Jiang
* @since 2023/11/9
*/
@ConfigurationProperties("renkang.batch")
@Data
public class RenkangBatchProperties {
private String jdbcUrl;
private String username;
private String password;
private String driverClassName;
private Integer writeSize = 100;
private boolean async = false;
}
@@ -0,0 +1,84 @@
package com.renkang.batch.job;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
/**
* @author Shunzhi Jiang
* @since 2023/11/11
*/
public abstract class AbstractSimpleTasklet implements Tasklet {
private ExecutionContext jobExecutionContext;
private ExecutionContext stepExecutionContext;
/**
* Task执行方法
*
* @return 返回 {@link ExitStatus#COMPLETED} 视为成功,其他均视为失败,将执行 {@link AbstractSimpleTasklet#onError(Throwable)} 方法
*/
public abstract ExitStatus doExecute();
/**
* 任务执行失败时,执行的方法,可以是 {@link AbstractSimpleTasklet#doExecute()} 方法
* 返回了失败或产生异常
*
* @param throwable 若返回了失败,则 {@link Throwable} 为null
* @throws Exception 抛出的异常,可以抛出自定义异常
*/
public abstract void onError(Throwable throwable) throws Exception;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
setExecutionContext(contribution);
try {
ExitStatus exitStatus = doExecute();
if (exitStatus == ExitStatus.COMPLETED) {
return setComplete(contribution, chunkContext);
} else {
contribution.setExitStatus(exitStatus);
onError(null);
}
} catch (Exception e) {
onError(e);
throw e;
}
return RepeatStatus.FINISHED;
}
public ExecutionContext getJobExecutionContext() {
return jobExecutionContext;
}
private void setJobExecutionContext(StepContribution contribution) {
jobExecutionContext = contribution.getStepExecution()
.getJobExecution()
.getExecutionContext();
}
public ExecutionContext getStepExecutionContext() {
return stepExecutionContext;
}
private void setStepExecutionContext(StepContribution contribution) {
stepExecutionContext = contribution.getStepExecution()
.getExecutionContext();
}
private void setExecutionContext(StepContribution contribution) {
setJobExecutionContext(contribution);
setStepExecutionContext(contribution);
}
private RepeatStatus setComplete(StepContribution contribution, ChunkContext chunkContext) {
contribution.setExitStatus(ExitStatus.COMPLETED);
chunkContext.setComplete();
return RepeatStatus.FINISHED;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.renkang</groupId>
<artifactId>functional-modules</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>renkang-test</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,77 @@
package com.renkang;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.LifecycleUtils;
import org.apache.shiro.util.ThreadState;
import org.junit.jupiter.api.AfterAll;
/**
* @author Jiang Shunzhi
*/
public abstract class AbstractShiroTest {
private static ThreadState subjectThreadState;
public AbstractShiroTest() {
}
private static void doClearSubject() {
if (subjectThreadState != null) {
subjectThreadState.clear();
subjectThreadState = null;
}
}
protected static org.apache.shiro.mgt.SecurityManager getSecurityManager() {
return SecurityUtils.getSecurityManager();
}
protected static void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
SecurityUtils.setSecurityManager(securityManager);
}
@AfterAll
public static void tearDownShiro() {
doClearSubject();
try {
SecurityManager securityManager = getSecurityManager();
LifecycleUtils.destroy(securityManager);
} catch (UnavailableSecurityManagerException e) {
//we don't care about this when cleaning up the test environment
//(for example, maybe the subclass is a unit test and it didn't
// need a SecurityManager instance because it was using only
// mock Subject instances)
}
setSecurityManager(null);
}
protected Subject getSubject() {
return SecurityUtils.getSubject();
}
/**
* Allows subclasses to set the currently executing {@link Subject} instance.
*
* @param subject the Subject instance
*/
protected void setSubject(Subject subject) {
clearSubject();
subjectThreadState = createThreadState(subject);
subjectThreadState.bind();
}
protected ThreadState createThreadState(Subject subject) {
return new SubjectThreadState(subject);
}
/**
* Clears Shiro's thread state, ensuring the thread remains clean for future test execution.
*/
protected void clearSubject() {
doClearSubject();
}
}
@@ -0,0 +1,91 @@
package com.renkang;
import cn.hutool.core.bean.BeanUtil;
import lombok.Getter;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.jeecg.common.api.CommonAPI;
import org.jeecg.common.config.mqtoken.UserTokenContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.config.shiro.JwtToken;
import org.jeecg.config.shiro.TokenThreadLocal;
import org.jeecg.global.GlobalUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import java.util.Objects;
/**
* @author Shunzhi Jiang
* @since 2024/11/9
*/
public abstract class AbstractTestSupport extends AbstractShiroTest {
@Autowired
@Getter
private CommonAPI commonAPI;
@Autowired
@Getter
private RedisUtil redisUtil;
@Autowired
private ApplicationContext applicationContext;
protected Subject adminSubject() {
Subject subject = new Subject.Builder().buildSubject();
String temporaryToken = GlobalUtils.getTemporaryToken();
TokenThreadLocal.setToken(temporaryToken);
UserTokenContext.setToken(temporaryToken);
subject.login(new JwtToken(temporaryToken));
return subject;
}
@BeforeEach
protected void setAdminSubject() {
checkSecurityManager();
setSubject(adminSubject());
}
protected void checkSecurityManager() {
try {
getSecurityManager();
} catch (UnavailableSecurityManagerException e) {
setSecurityManager(applicationContext.getBean(SecurityManager.class));
}
}
@AfterEach
protected void clearTokenCache() {
String token = getToken();
redisUtil.del(CommonConstant.FAKE_USER_KEY_PREFIX + token, CommonConstant.PREFIX_USER_TOKEN + token);
TokenThreadLocal.clear();
UserTokenContext.remove();
}
protected String getToken() {
return TokenThreadLocal.getToken();
}
protected <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
protected void fakeUser(String userId) {
LoginUser loginUserById = TokenUtils.getLoginUserById(userId, commonAPI, redisUtil);
if (Objects.isNull(loginUserById)) {
throw new RuntimeException("未获取到模拟用户数据");
}
Subject subject = getSubject();
Object principal = subject.getPrincipal();
BeanUtil.copyProperties(loginUserById, principal);
}
}

Some files were not shown because too many files have changed in this diff Show More