新疆后端项目

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
+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}