mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-05 02:19:56 +08:00
云快充1.5.0 初始化
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class})
|
||||
@MapperScan({"sanbing.jcpp.app.dal.mapper"})
|
||||
public class DalConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource")
|
||||
public DataSourceProperties dataSourceProperties() {
|
||||
return new DataSourceProperties();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@ConfigurationProperties(prefix = "spring.datasource.hikari")
|
||||
@Bean
|
||||
public DataSource dataSource(@Qualifier("dataSourceProperties") DataSourceProperties dataSourceProperties) {
|
||||
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
|
||||
return new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setIsolationLevel(TransactionTemplate.ISOLATION_READ_COMMITTED);
|
||||
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
|
||||
return transactionTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
paginationInnerInterceptor.setDbType(DbType.POSTGRE_SQL);
|
||||
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
return mybatisPlusInterceptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum GunOptStatusEnum implements IEnum<String> {
|
||||
AVAILABLE, // 可用状态
|
||||
IN_MAINTENANCE, // 维护中状态
|
||||
OUT_OF_SERVICE, // 停用状态
|
||||
RESERVED; // 已预约状态
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum GunRunStatusEnum implements IEnum<String> {
|
||||
IDLE, // 空闲
|
||||
INSERTED, // 已插枪
|
||||
CHARGING, // 充电中
|
||||
CHARGE_COMPLETE, // 充电完成
|
||||
DISCHARGE_READY, // 放电准备
|
||||
DISCHARGING, // 放电中
|
||||
DISCHARGE_COMPLETE, // 放电完成
|
||||
RESERVED, // 预约
|
||||
FAULT; // 故障
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
public enum OrderStatusEnum implements IEnum<String> {
|
||||
PENDING,
|
||||
IN_CHARGING,
|
||||
COMPLETED,
|
||||
CANCELLED,
|
||||
TERMINATED,
|
||||
FAILED,
|
||||
REFUNDED;
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum OrderTypeEnum implements IEnum<String> {
|
||||
CHARGE,
|
||||
|
||||
DISCHARGE;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum OwnerTypeEnum implements IEnum<String> {
|
||||
C,
|
||||
B,
|
||||
G;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum PileStatusEnum implements IEnum<String> {
|
||||
IDLE, // 空闲
|
||||
WORKING, // 工作中
|
||||
FAULT, // 故障
|
||||
MAINTENANCE, // 维护中
|
||||
OFFLINE, // 离线
|
||||
;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum PileTypeEnum implements IEnum<String> {
|
||||
AC, // 交流充电桩
|
||||
DC, // 直流充电桩
|
||||
;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum StationStatusEnum implements IEnum<String> {
|
||||
OPERATIONAL, // 正常运营
|
||||
PARTIAL_FAILURE, // 部分故障
|
||||
FULLY_LOADED, // 满载
|
||||
MAINTENANCE, // 维护中
|
||||
CLOSED, // 关闭
|
||||
WAITING_FOR_OPEN; // 待开放
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public enum UserStatusEnum implements IEnum<String> {
|
||||
ENABLE,
|
||||
DISABLE;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.typehandlers;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.postgresql.util.PGobject;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@Slf4j
|
||||
@MappedTypes({JsonNode.class})
|
||||
public class JsonbTypeHandler extends JacksonTypeHandler {
|
||||
|
||||
public JsonbTypeHandler(Class<?> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
public JsonbTypeHandler(Class<?> type, Field field) {
|
||||
super(type, field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
|
||||
if (ps != null) {
|
||||
PGobject jsonObject = new PGobject();
|
||||
jsonObject.setType("jsonb");
|
||||
jsonObject.setValue(JacksonUtil.toString(parameter));
|
||||
ps.setObject(i, jsonObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.typehandlers;
|
||||
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* mysql UUID 类型转 varchar
|
||||
*/
|
||||
public class UUIDTypeHandler extends BaseTypeHandler<UUID> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) throws SQLException {
|
||||
ps.setObject(i, parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return rs.getObject(columnName, UUID.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return rs.getObject(columnIndex, UUID.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return cs.getObject(columnIndex, UUID.class);
|
||||
}
|
||||
}
|
||||
61
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java
Normal file
61
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.GunOptStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_gun")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Gun implements Serializable, HasVersion {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private String gunNo;
|
||||
|
||||
private String gunName;
|
||||
|
||||
private String gunCode;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private UUID pileId;
|
||||
|
||||
private UUID ownerId;
|
||||
|
||||
private OwnerTypeEnum ownerType;
|
||||
|
||||
private GunRunStatusEnum runStatus;
|
||||
|
||||
private LocalDateTime runStatusUpdatedTime;
|
||||
|
||||
private GunOptStatusEnum optStatus;
|
||||
|
||||
private Integer version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.OrderStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.OrderTypeEnum;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_order")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Order implements Serializable {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private String internalOrderNo;
|
||||
|
||||
private String externalOrderNo;
|
||||
|
||||
private String pileOrderNo;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
private LocalDateTime cancelledTime;
|
||||
|
||||
private OrderStatusEnum status;
|
||||
|
||||
private OrderTypeEnum type;
|
||||
|
||||
private UUID creatorId;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private UUID pileId;
|
||||
|
||||
private UUID gunId;
|
||||
|
||||
private String plateNo;
|
||||
|
||||
private Long settlementAmount;
|
||||
|
||||
private JsonNode settlementDetails;
|
||||
|
||||
private BigDecimal electricityQuantity;
|
||||
|
||||
|
||||
}
|
||||
61
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java
Normal file
61
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@TableName(value = "jcpp_pile", autoResultMap = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Pile implements Serializable, HasVersion {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private String pileName;
|
||||
|
||||
private String pileCode;
|
||||
|
||||
private String protocol;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private UUID ownerId;
|
||||
|
||||
private OwnerTypeEnum ownerType;
|
||||
|
||||
private String brand;
|
||||
|
||||
private String model;
|
||||
|
||||
private String manufacturer;
|
||||
|
||||
private PileStatusEnum status;
|
||||
|
||||
private PileTypeEnum type;
|
||||
|
||||
private Integer version;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.StationStatusEnum;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_station")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Station implements Serializable, HasVersion {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private String stationName;
|
||||
|
||||
private String stationCode;
|
||||
|
||||
private UUID ownerId;
|
||||
|
||||
private Float longitude;
|
||||
|
||||
private Float latitude;
|
||||
|
||||
private OwnerTypeEnum ownerType;
|
||||
|
||||
private String province;
|
||||
|
||||
private String city;
|
||||
|
||||
private String county;
|
||||
|
||||
private String address;
|
||||
|
||||
private StationStatusEnum status;
|
||||
|
||||
private Integer version;
|
||||
|
||||
}
|
||||
45
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java
Normal file
45
jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_user")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class User implements Serializable, HasVersion {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private UserStatusEnum status;
|
||||
|
||||
private String userName;
|
||||
|
||||
private JsonNode userCredentials;
|
||||
|
||||
private Integer version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import sanbing.jcpp.app.dal.entity.Gun;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface GunMapper extends BaseMapper<Gun> {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import sanbing.jcpp.app.dal.entity.Order;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface OrderMapper extends BaseMapper<Order> {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface PileMapper extends BaseMapper<Pile> {
|
||||
|
||||
@Select("SELECT " +
|
||||
" * " +
|
||||
"FROM " +
|
||||
" jcpp_pile " +
|
||||
"WHERE " +
|
||||
" pile_code = #{pileCode}")
|
||||
Pile selectByCode(String pileCode);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface StationMapper extends BaseMapper<Station> {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import sanbing.jcpp.app.dal.entity.User;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Data
|
||||
public class PileSession implements Serializable {
|
||||
|
||||
private final UUID pileId;
|
||||
|
||||
private final String pileCode;
|
||||
|
||||
private final String protocolName;
|
||||
|
||||
private UUID protocolSessionId;
|
||||
|
||||
private String remoteAddress;
|
||||
|
||||
private String nodeId;
|
||||
|
||||
private String nodeWebapiIpPort;
|
||||
|
||||
public PileSession(UUID pileId, String pileCode, String protocolName) {
|
||||
this.pileId = pileId;
|
||||
this.pileCode = pileCode;
|
||||
this.protocolName = protocolName;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public PileSession(
|
||||
@JsonProperty("pileId") UUID pileId,
|
||||
@JsonProperty("pileCode") String pileCode,
|
||||
@JsonProperty("protocolName") String protocolName,
|
||||
@JsonProperty("protocolSessionId") UUID protocolSessionId,
|
||||
@JsonProperty("remoteAddress") String remoteAddress,
|
||||
@JsonProperty("nodeId") String nodeId,
|
||||
@JsonProperty("nodeWebapiIpPort") String nodeWebapiIpPort) {
|
||||
this.pileId = pileId;
|
||||
this.pileCode = pileCode;
|
||||
this.protocolName = protocolName;
|
||||
this.protocolSessionId = protocolSessionId;
|
||||
this.remoteAddress = remoteAddress;
|
||||
this.nodeId = nodeId;
|
||||
this.nodeWebapiIpPort = nodeWebapiIpPort;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class AbstractCachedEntityRepository<K extends Serializable, V extends Serializable, E> extends AbstractEntityRepository {
|
||||
|
||||
protected void publishEvictEvent(E event) {
|
||||
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
eventPublisher.publishEvent(event);
|
||||
} else {
|
||||
handleEvictEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void handleEvictEvent(E event);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractEntityRepository {
|
||||
|
||||
@Resource
|
||||
protected ApplicationEventPublisher eventPublisher;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCache;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCacheKey;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class CachedVersionedEntityRepository<K extends VersionedCacheKey, V extends Serializable & HasVersion, E> extends AbstractCachedEntityRepository<K, V, E> {
|
||||
|
||||
@Resource
|
||||
protected VersionedCache<K, V> cache;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface PileRepository {
|
||||
|
||||
Pile findPileByCode(String pileCode);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.app.dal.mapper.PileMapper;
|
||||
import sanbing.jcpp.app.service.cache.pile.PileCacheEvictEvent;
|
||||
import sanbing.jcpp.app.service.cache.pile.PileCacheKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.util.validation.Validator.validateString;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Repository
|
||||
@Slf4j
|
||||
public class PileRepositoryImpl extends CachedVersionedEntityRepository<PileCacheKey, Pile, PileCacheEvictEvent> implements PileRepository {
|
||||
|
||||
@Resource
|
||||
PileMapper pileMapper;
|
||||
|
||||
@TransactionalEventListener(classes = PileCacheEvictEvent.class)
|
||||
@Override
|
||||
public void handleEvictEvent(PileCacheEvictEvent event) {
|
||||
// 如果修改或删除充电桩,需要在这里消费删除事件
|
||||
List<PileCacheKey> toEvict = new ArrayList<>(3);
|
||||
toEvict.add(new PileCacheKey(event.getPileId()));
|
||||
toEvict.add(new PileCacheKey(event.getPileCode()));
|
||||
cache.evict(toEvict);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pile findPileByCode(String pileCode) {
|
||||
validateString(pileCode, code -> "无效的桩编号" + pileCode);
|
||||
return cache.get(new PileCacheKey(pileCode),
|
||||
() -> pileMapper.selectByCode(pileCode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service;
|
||||
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRestMessage;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface DownlinkCallService {
|
||||
|
||||
void sendDownlinkMessage(DownlinkRestMessage.Builder downlinkMessageBuilder, String pileCode);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service;
|
||||
|
||||
import sanbing.jcpp.infrastructure.queue.Callback;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
public interface PileProtocolService {
|
||||
/**
|
||||
* 桩登录
|
||||
*/
|
||||
void pileLogin(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 充电桩心跳
|
||||
*/
|
||||
void heartBeat(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 校验计费模型
|
||||
*/
|
||||
void verifyPricing(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 查询计费策略
|
||||
*/
|
||||
void queryPricing(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 上报电桩运行状态
|
||||
*/
|
||||
void postGunRunStatus(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 上报充电进度
|
||||
*/
|
||||
void postChargingProgress(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 费率下发反馈
|
||||
*/
|
||||
void onSetPricingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 远程启动反馈
|
||||
*
|
||||
* @param uplinkQueueMessage
|
||||
* @param callback
|
||||
*/
|
||||
void onRemoteStartChargingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 远程停止反馈
|
||||
*/
|
||||
void onRemoteStopChargingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
|
||||
/**
|
||||
* 交易记录上报
|
||||
*/
|
||||
void onTransactionRecord(UplinkQueueMessage uplinkQueueMessage, Callback callback);
|
||||
}
|
||||
17
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheEvictEvent.java
vendored
Normal file
17
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheEvictEvent.java
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.pile;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class PileCacheEvictEvent {
|
||||
|
||||
private UUID pileId;
|
||||
private String pileCode;
|
||||
|
||||
}
|
||||
47
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java
vendored
Normal file
47
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.pile;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCacheKey;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
@Builder
|
||||
public class PileCacheKey implements VersionedCacheKey {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 6366389552842340207L;
|
||||
|
||||
private final UUID pileId;
|
||||
private final String pileCode;
|
||||
|
||||
public PileCacheKey(UUID pileId) {
|
||||
this(pileId, null);
|
||||
}
|
||||
|
||||
public PileCacheKey(String pileCode) {
|
||||
this(null, pileCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Optional.ofNullable(pileId).map(UUID::toString).orElse(pileCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersioned() {
|
||||
return pileId != null;
|
||||
}
|
||||
|
||||
}
|
||||
22
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCaffeineCache.java
vendored
Normal file
22
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCaffeineCache.java
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.pile;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.infrastructure.cache.CacheConstants;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCaffeineCache;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@Service("PileCache")
|
||||
public class PileCaffeineCache extends VersionedCaffeineCache<PileCacheKey, Pile> {
|
||||
|
||||
public PileCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.PILE_CACHE);
|
||||
}
|
||||
|
||||
}
|
||||
33
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileRedisCache.java
vendored
Normal file
33
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileRedisCache.java
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.pile;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.infrastructure.cache.*;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@Service("PileCache")
|
||||
public class PileRedisCache extends VersionedRedisCache<PileCacheKey, Pile> {
|
||||
|
||||
public PileRedisCache(JCPPRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, LettuceConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.PILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new JCPPRedisSerializer<>() {
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Pile pile) throws SerializationException {
|
||||
return JacksonUtil.writeValueAsBytes(pile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pile deserialize(PileCacheKey key, byte[] bytes) throws SerializationException {
|
||||
return JacksonUtil.fromBytes(bytes, Pile.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
41
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java
vendored
Normal file
41
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.session;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
@Builder
|
||||
public class PileSessionCacheKey implements Serializable {
|
||||
|
||||
private final UUID pileId;
|
||||
private final String pileCode;
|
||||
|
||||
public PileSessionCacheKey(UUID pileId) {
|
||||
this(pileId, null);
|
||||
}
|
||||
|
||||
public PileSessionCacheKey(String pileCode) {
|
||||
this(null, pileCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Optional.ofNullable(pileId).map(UUID::toString).orElse(pileCode);
|
||||
}
|
||||
|
||||
}
|
||||
24
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java
vendored
Normal file
24
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.session;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.data.PileSession;
|
||||
import sanbing.jcpp.infrastructure.cache.CacheConstants;
|
||||
import sanbing.jcpp.infrastructure.cache.CaffeineTransactionalCache;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@Service("PileSessionCache")
|
||||
public class PileSessionCaffeineCache extends CaffeineTransactionalCache<PileSessionCacheKey, PileSession> {
|
||||
|
||||
public PileSessionCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.PILE_SESSION_CACHE);
|
||||
}
|
||||
}
|
||||
36
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java
vendored
Normal file
36
jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.cache.session;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.data.PileSession;
|
||||
import sanbing.jcpp.infrastructure.cache.*;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@Service("PileSessionCache")
|
||||
public class PileSessionRedisCache extends RedisTransactionalCache<PileSessionCacheKey, PileSession> {
|
||||
|
||||
public PileSessionRedisCache(JCPPRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, LettuceConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.PILE_SESSION_CACHE, cacheSpecsMap, connectionFactory, configuration, new JCPPRedisSerializer<>() {
|
||||
|
||||
@Override
|
||||
public byte[] serialize(PileSession pileSession) throws SerializationException {
|
||||
return JacksonUtil.writeValueAsBytes(pileSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PileSession deserialize(PileSessionCacheKey key, byte[] bytes) throws SerializationException {
|
||||
return JacksonUtil.fromBytes(bytes, PileSession.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.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.protobuf.ProtobufHttpMessageConverter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Configuration
|
||||
public class DownlinkRestTemplateConfiguration {
|
||||
|
||||
@Bean("downlinkRestTemplate")
|
||||
public RestTemplate downlinkRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplateBuilder()
|
||||
.setConnectTimeout(Duration.of(3, ChronoUnit.SECONDS))
|
||||
.setReadTimeout(Duration.of(3, ChronoUnit.SECONDS))
|
||||
.build();
|
||||
restTemplate.setMessageConverters(Collections.singletonList(new ProtobufHttpMessageConverter()));
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import sanbing.jcpp.app.data.PileSession;
|
||||
import sanbing.jcpp.app.service.DownlinkCallService;
|
||||
import sanbing.jcpp.app.service.cache.session.PileSessionCacheKey;
|
||||
import sanbing.jcpp.infrastructure.cache.CacheValueWrapper;
|
||||
import sanbing.jcpp.infrastructure.cache.TransactionalCache;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.ServiceInfoProvider;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRestMessage;
|
||||
import sanbing.jcpp.protocol.adapter.DownlinkController;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.util.trace.TracerContextUtil.*;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DefaultDownlinkCallService implements DownlinkCallService {
|
||||
|
||||
@Resource
|
||||
RestTemplate downlinkRestTemplate;
|
||||
|
||||
@Resource
|
||||
ServiceInfoProvider serviceInfoProvider;
|
||||
|
||||
@Resource
|
||||
DownlinkController downlinkController;
|
||||
|
||||
@Resource
|
||||
TransactionalCache<PileSessionCacheKey, PileSession> pileSessionCache;
|
||||
|
||||
@Override
|
||||
public void sendDownlinkMessage(DownlinkRestMessage.Builder downlinkMessageBuilder, String pileCode) {
|
||||
if (serviceInfoProvider.isMonolith()) {
|
||||
|
||||
downlinkController.onDownlink(downlinkMessageBuilder.build())
|
||||
.setResultHandler(result -> log.info("下行消息发送完成"));
|
||||
|
||||
} else {
|
||||
try {
|
||||
CacheValueWrapper<PileSession> pileSessionCacheValueWrapper = pileSessionCache.get(new PileSessionCacheKey(pileCode));
|
||||
|
||||
if (pileSessionCacheValueWrapper == null) {
|
||||
log.warn("充电桩会话不存在 {}", pileCode);
|
||||
return;
|
||||
}
|
||||
|
||||
PileSession pileSession = pileSessionCacheValueWrapper.get();
|
||||
|
||||
invokeDownlinkRestApi(downlinkMessageBuilder.build(), pileSession.getNodeWebapiIpPort());
|
||||
|
||||
|
||||
} catch (RestClientException e) {
|
||||
log.error("下行消息发送异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeDownlinkRestApi(DownlinkRestMessage downlinkRestMessage, String nodeWebapiIpPort) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(JCPP_TRACER_ID, TracerContextUtil.getCurrentTracer().getTraceId());
|
||||
headers.add(JCPP_TRACER_ORIGIN, TracerContextUtil.getCurrentTracer().getOrigin());
|
||||
headers.add(JCPP_TRACER_TS, String.valueOf(TracerContextUtil.getCurrentTracer().getTracerTs()));
|
||||
headers.setContentType(MediaType.parseMediaType("application/x-protobuf"));
|
||||
|
||||
HttpEntity<DownlinkRestMessage> entity = new HttpEntity<>(downlinkRestMessage, headers);
|
||||
|
||||
try {
|
||||
ResponseEntity<?> response = downlinkRestTemplate.postForEntity("http://" + nodeWebapiIpPort + "/api/onDownlink",
|
||||
entity, ResponseEntity.class);
|
||||
log.info("下行消息发送成功 {}", response);
|
||||
} catch (RestClientException e) {
|
||||
log.error("下行消息发送失败 {}", downlinkRestMessage, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.app.data.PileSession;
|
||||
import sanbing.jcpp.app.repository.PileRepository;
|
||||
import sanbing.jcpp.app.service.DownlinkCallService;
|
||||
import sanbing.jcpp.app.service.PileProtocolService;
|
||||
import sanbing.jcpp.app.service.cache.session.PileSessionCacheKey;
|
||||
import sanbing.jcpp.infrastructure.cache.TransactionalCache;
|
||||
import sanbing.jcpp.infrastructure.proto.ProtoConverter;
|
||||
import sanbing.jcpp.infrastructure.proto.model.PricingModel;
|
||||
import sanbing.jcpp.infrastructure.proto.model.PricingModel.FlagPrice;
|
||||
import sanbing.jcpp.infrastructure.proto.model.PricingModel.Period;
|
||||
import sanbing.jcpp.infrastructure.queue.Callback;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.*;
|
||||
import sanbing.jcpp.protocol.domain.DownlinkCmdEnum;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.*;
|
||||
|
||||
import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelFlag.*;
|
||||
import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelRule.SPLIT_TIME;
|
||||
import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelType.CHARGE;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DefaultPileProtocolService implements PileProtocolService {
|
||||
|
||||
@Resource
|
||||
PileRepository pileRepository;
|
||||
|
||||
@Resource
|
||||
TransactionalCache<PileSessionCacheKey, PileSession> pileSessionCache;
|
||||
|
||||
@Resource
|
||||
DownlinkCallService downlinkCallService;
|
||||
|
||||
@Override
|
||||
public void pileLogin(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到桩登录事件 {}", uplinkQueueMessage);
|
||||
|
||||
LoginRequest loginRequest = uplinkQueueMessage.getLoginRequest();
|
||||
|
||||
Pile pile = pileRepository.findPileByCode(loginRequest.getPileCode());
|
||||
|
||||
String pileCode = loginRequest.getPileCode();
|
||||
|
||||
log.info("查询到充电桩信息 {}", pile);
|
||||
|
||||
// 构造下行回复
|
||||
DownlinkRestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, loginRequest.getPileCode());
|
||||
downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.LOGIN_ACK.name());
|
||||
|
||||
if (pile != null) {
|
||||
// 保存到缓存
|
||||
cacheSession(uplinkQueueMessage, pile,
|
||||
loginRequest.getRemoteAddress(),
|
||||
loginRequest.getNodeId(),
|
||||
loginRequest.getNodeWebapiIpPort());
|
||||
|
||||
downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder()
|
||||
.setSuccess(true)
|
||||
.setPileCode(loginRequest.getPileCode())
|
||||
.build());
|
||||
} else {
|
||||
downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder()
|
||||
.setSuccess(false)
|
||||
.setPileCode(loginRequest.getPileCode())
|
||||
.build());
|
||||
}
|
||||
|
||||
downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void heartBeat(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到桩心跳事件 {}", uplinkQueueMessage);
|
||||
|
||||
HeartBeatRequest heartBeatRequest = uplinkQueueMessage.getHeartBeatRequest();
|
||||
|
||||
Pile pile = pileRepository.findPileByCode(heartBeatRequest.getPileCode());
|
||||
|
||||
if (pile != null) {
|
||||
// 重新保存到缓存
|
||||
cacheSession(uplinkQueueMessage, pile,
|
||||
heartBeatRequest.getRemoteAddress(),
|
||||
heartBeatRequest.getNodeId(),
|
||||
heartBeatRequest.getNodeWebapiIpPort());
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheSession(UplinkQueueMessage uplinkQueueMessage, Pile pile, String remoteAddress, String nodeId, String nodeWebapiIpPort) {
|
||||
PileSession pileSession = new PileSession(pile.getId(), pile.getPileCode(), uplinkQueueMessage.getProtocolName());
|
||||
pileSession.setProtocolSessionId(new UUID(uplinkQueueMessage.getSessionIdMSB(), uplinkQueueMessage.getSessionIdLSB()));
|
||||
pileSession.setRemoteAddress(remoteAddress);
|
||||
pileSession.setNodeId(nodeId);
|
||||
pileSession.setNodeWebapiIpPort(nodeWebapiIpPort);
|
||||
pileSessionCache.put(new PileSessionCacheKey(pile.getId()), pileSession);
|
||||
pileSessionCache.put(new PileSessionCacheKey(pile.getPileCode()), pileSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyPricing(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到计费模型验证请求 {}", uplinkQueueMessage);
|
||||
|
||||
VerifyPricingRequest verifyPricingRequest = uplinkQueueMessage.getVerifyPricingRequest();
|
||||
String pileCode = verifyPricingRequest.getPileCode();
|
||||
|
||||
long pricingId = verifyPricingRequest.getPricingId();
|
||||
// todo 默认校验成功,后续查库校验
|
||||
assert pricingId > 0;
|
||||
|
||||
DownlinkRestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, pileCode);
|
||||
downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.VERIFY_PRICING_ACK.name());
|
||||
downlinkMessageBuilder.setVerifyPricingResponse(VerifyPricingResponse.newBuilder()
|
||||
.setSuccess(true)
|
||||
.setPricingId(pricingId)
|
||||
.build());
|
||||
|
||||
downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPricing(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩计费模型请求 {}", uplinkQueueMessage);
|
||||
|
||||
QueryPricingRequest queryPricingRequest = uplinkQueueMessage.getQueryPricingRequest();
|
||||
String pileCode = queryPricingRequest.getPileCode();
|
||||
|
||||
// TODO 先构造一个通用的计费模型,后续根据业务做库查询
|
||||
List<Period> periods = new ArrayList<>();
|
||||
|
||||
periods.add(createPeriod(1, LocalTime.parse("00:00"), LocalTime.parse("06:00"), TOP));
|
||||
periods.add(createPeriod(2, LocalTime.parse("06:00"), LocalTime.parse("12:00"), PEAK));
|
||||
periods.add(createPeriod(3, LocalTime.parse("12:00"), LocalTime.parse("18:00"), FLAT));
|
||||
periods.add(createPeriod(4, LocalTime.parse("18:00"), LocalTime.parse("00:00"), VALLEY));
|
||||
|
||||
Map<PricingModelFlag, FlagPrice> flagPriceMap = new HashMap<>();
|
||||
flagPriceMap.put(TOP, new FlagPrice(75, 45));
|
||||
flagPriceMap.put(PEAK, new FlagPrice(75, 45));
|
||||
flagPriceMap.put(FLAT, new FlagPrice(75, 45));
|
||||
flagPriceMap.put(VALLEY, new FlagPrice(75, 45));
|
||||
|
||||
PricingModel model = new PricingModel();
|
||||
model.setId(UUID.randomUUID());
|
||||
model.setSequenceNumber(1);
|
||||
model.setPileCode(pileCode);
|
||||
model.setType(CHARGE);
|
||||
model.setRule(SPLIT_TIME);
|
||||
model.setStandardElec(75);
|
||||
model.setStandardServ(45);
|
||||
model.setFlagPriceList(flagPriceMap);
|
||||
model.setPeriodsList(periods);
|
||||
|
||||
// 构造下行计费
|
||||
DownlinkRestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, pileCode);
|
||||
downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.QUERY_PRICING_ACK.name());
|
||||
downlinkMessageBuilder.setQueryPricingResponse(QueryPricingResponse.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setPricingId(model.getSequenceNumber())
|
||||
.setPricingModel(ProtoConverter.toPricingModel(model))
|
||||
.build());
|
||||
|
||||
downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postGunRunStatus(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩上报的电桩状态 {}", uplinkQueueMessage);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postChargingProgress(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩上报的充电进度 {}", uplinkQueueMessage);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetPricingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩上费率下发反馈 {}", uplinkQueueMessage);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteStartChargingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩启动结果反馈 {}", uplinkQueueMessage);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteStopChargingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩停止结果反馈 {}", uplinkQueueMessage);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionRecord(UplinkQueueMessage uplinkQueueMessage, Callback callback) {
|
||||
log.info("接收到充电桩交易记录上报 {}", uplinkQueueMessage);
|
||||
|
||||
// todo 毛都不敢先给个回复
|
||||
TransactionRecord transactionRecord = uplinkQueueMessage.getTransactionRecord();
|
||||
|
||||
String tradeNo = transactionRecord.getTradeNo();
|
||||
String pileCode = transactionRecord.getPileCode();
|
||||
|
||||
// 构造下行计费
|
||||
DownlinkRestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, pileCode);
|
||||
downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.TRANSACTION_RECORD.name());
|
||||
downlinkMessageBuilder.setTransactionRecordAck(TransactionRecordAck.newBuilder()
|
||||
.setTradeNo(tradeNo)
|
||||
.setSuccess(true)
|
||||
.build());
|
||||
|
||||
downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode);
|
||||
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
private static Period createPeriod(int sn, LocalTime beginTime, LocalTime endTime, PricingModelFlag flag) {
|
||||
Period period = new Period();
|
||||
period.setSn(sn);
|
||||
period.setBegin(beginTime);
|
||||
period.setEnd(endTime);
|
||||
period.setFlag(flag);
|
||||
return period;
|
||||
}
|
||||
|
||||
private DownlinkRestMessage.Builder createDownlinkMessageBuilder(UplinkQueueMessage uplinkQueueMessage, String pileCode) {
|
||||
UUID messageId = UUID.randomUUID();
|
||||
DownlinkRestMessage.Builder builder = DownlinkRestMessage.newBuilder();
|
||||
builder.setMessageIdMSB(messageId.getLeastSignificantBits());
|
||||
builder.setMessageIdLSB(messageId.getLeastSignificantBits());
|
||||
builder.setPileCode(pileCode);
|
||||
builder.setSessionIdMSB(uplinkQueueMessage.getSessionIdMSB());
|
||||
builder.setSessionIdLSB(uplinkQueueMessage.getSessionIdLSB());
|
||||
builder.setProtocolName(uplinkQueueMessage.getProtocolName());
|
||||
builder.setRequestIdMSB(uplinkQueueMessage.getMessageIdMSB());
|
||||
builder.setRequestIdLSB(uplinkQueueMessage.getMessageIdLSB());
|
||||
builder.setRequestData(uplinkQueueMessage.getRequestData());
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.PartitionProvider;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.event.JCPPApplicationEventListener;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.event.PartitionChangeEvent;
|
||||
import sanbing.jcpp.infrastructure.util.annotation.AfterStartUp;
|
||||
import sanbing.jcpp.infrastructure.util.async.JCPPExecutors;
|
||||
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractConsumerService extends JCPPApplicationEventListener<PartitionChangeEvent> {
|
||||
|
||||
protected final PartitionProvider partitionProvider;
|
||||
protected final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
protected ExecutorService consumersExecutor;
|
||||
protected ExecutorService mgmtExecutor;
|
||||
protected ScheduledExecutorService scheduler;
|
||||
|
||||
public void init(String prefix) {
|
||||
this.consumersExecutor = Executors.newCachedThreadPool(JCPPThreadFactory.forName(prefix + "-consumer"));
|
||||
this.mgmtExecutor = JCPPExecutors.newWorkStealingPool(getMgmtThreadPoolSize(), prefix + "-mgmt");
|
||||
this.scheduler = Executors.newSingleThreadScheduledExecutor(JCPPThreadFactory.forName(prefix + "-consumer-scheduler"));
|
||||
}
|
||||
|
||||
@AfterStartUp(order = AfterStartUp.REGULAR_SERVICE)
|
||||
public void afterStartUp() {
|
||||
startConsumers();
|
||||
}
|
||||
|
||||
protected void startConsumers() {
|
||||
}
|
||||
|
||||
protected void stopConsumers() {
|
||||
}
|
||||
|
||||
protected abstract int getMgmtThreadPoolSize();
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
stopConsumers();
|
||||
if (consumersExecutor != null) {
|
||||
consumersExecutor.shutdownNow();
|
||||
}
|
||||
if (mgmtExecutor != null) {
|
||||
mgmtExecutor.shutdownNow();
|
||||
}
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.stats.StatsCounter;
|
||||
import sanbing.jcpp.infrastructure.stats.StatsFactory;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class AppConsumerStats {
|
||||
public static final String TOTAL_MSGS = "totalMsgs";
|
||||
public static final String LOGIN_EVENTS = "loginEvents";
|
||||
public static final String HEARTBEAT_EVENTS = "heartBeatEvents";
|
||||
public static final String GUN_RUN_STATUS_EVENTS = "gunRunStatusEvents";
|
||||
public static final String CHARGING_PROGRESS_EVENTS = "chargingProgressEvents";
|
||||
public static final String TRANSACTION_RECORD_EVENTS = "transactionRecordEvents";
|
||||
|
||||
private final StatsCounter totalCounter;
|
||||
private final StatsCounter loginCounter;
|
||||
private final StatsCounter heartBeatCounter;
|
||||
private final StatsCounter gunRunStatusCounter;
|
||||
private final StatsCounter chargingProgressCounter;
|
||||
private final StatsCounter transactionRecordCounter;
|
||||
private final Timer appConsumerTimer;
|
||||
|
||||
private final List<StatsCounter> counters = new ArrayList<>();
|
||||
|
||||
public AppConsumerStats(StatsFactory statsFactory) {
|
||||
String statsKey = "appConsumer";
|
||||
|
||||
this.totalCounter = register(statsFactory.createStatsCounter(statsKey, TOTAL_MSGS));
|
||||
this.loginCounter = register(statsFactory.createStatsCounter(statsKey, LOGIN_EVENTS));
|
||||
this.heartBeatCounter = register(statsFactory.createStatsCounter(statsKey, HEARTBEAT_EVENTS));
|
||||
this.gunRunStatusCounter = register(statsFactory.createStatsCounter(statsKey, GUN_RUN_STATUS_EVENTS));
|
||||
this.chargingProgressCounter = register(statsFactory.createStatsCounter(statsKey, CHARGING_PROGRESS_EVENTS));
|
||||
this.transactionRecordCounter = register(statsFactory.createStatsCounter(statsKey, TRANSACTION_RECORD_EVENTS));
|
||||
this.appConsumerTimer = statsFactory.createTimer(statsKey);
|
||||
}
|
||||
|
||||
private StatsCounter register(StatsCounter counter) {
|
||||
counters.add(counter);
|
||||
return counter;
|
||||
}
|
||||
|
||||
public void log(UplinkQueueMessage msg) {
|
||||
totalCounter.increment();
|
||||
if (msg.hasLoginRequest()) {
|
||||
loginCounter.increment();
|
||||
} else if (msg.hasHeartBeatRequest()) {
|
||||
heartBeatCounter.increment();
|
||||
} else if (msg.hasGunRunStatusProto()) {
|
||||
gunRunStatusCounter.increment();
|
||||
} else if (msg.hasChargingProgressProto()) {
|
||||
chargingProgressCounter.increment();
|
||||
} else if (msg.hasTransactionRecord()) {
|
||||
transactionRecordCounter.increment();
|
||||
}
|
||||
|
||||
appConsumerTimer.record(Duration.ofMillis(System.currentTimeMillis() - TracerContextUtil.getCurrentTracer().getTracerTs()));
|
||||
}
|
||||
|
||||
public void printStats() {
|
||||
int total = totalCounter.get();
|
||||
if (total > 0) {
|
||||
StringBuilder stats = new StringBuilder();
|
||||
counters.forEach(counter -> {
|
||||
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
|
||||
});
|
||||
log.info("App Queue Consumer Stats: {}", stats);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
counters.forEach(StatsCounter::clear);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.queue.QueueConsumer;
|
||||
import sanbing.jcpp.infrastructure.queue.QueueMsg;
|
||||
import sanbing.jcpp.infrastructure.queue.common.QueueConfig;
|
||||
import sanbing.jcpp.infrastructure.queue.common.TopicPartitionInfo;
|
||||
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class AppQueueConsumerManager<M extends QueueMsg, C extends QueueConfig> {
|
||||
|
||||
protected final String queueName;
|
||||
@Getter
|
||||
protected C config;
|
||||
protected final MsgPackProcessor<M, C> msgPackProcessor;
|
||||
protected final BiFunction<C, Integer, QueueConsumer<M>> consumerCreator;
|
||||
protected final ExecutorService consumerExecutor;
|
||||
protected final ScheduledExecutorService scheduler;
|
||||
protected final ExecutorService taskExecutor;
|
||||
|
||||
private final Queue<QueueConsumerManagerTask> tasks = new ConcurrentLinkedQueue<>();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
@Getter
|
||||
private volatile Set<TopicPartitionInfo> partitions;
|
||||
protected volatile ConsumerWrapper<M> consumerWrapper;
|
||||
protected volatile boolean stopped;
|
||||
|
||||
@Builder
|
||||
public AppQueueConsumerManager(String queueName, C config,
|
||||
MsgPackProcessor<M, C> msgPackProcessor,
|
||||
BiFunction<C, Integer, QueueConsumer<M>> consumerCreator,
|
||||
ExecutorService consumerExecutor,
|
||||
ScheduledExecutorService scheduler,
|
||||
ExecutorService taskExecutor) {
|
||||
this.queueName = queueName;
|
||||
this.config = config;
|
||||
this.msgPackProcessor = msgPackProcessor;
|
||||
this.consumerCreator = consumerCreator;
|
||||
this.consumerExecutor = consumerExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.taskExecutor = taskExecutor;
|
||||
if (config != null) {
|
||||
init(config);
|
||||
}
|
||||
}
|
||||
|
||||
public void init(C config) {
|
||||
this.config = config;
|
||||
if (config.isConsumerPerPartition()) {
|
||||
this.consumerWrapper = new ConsumerPerPartitionWrapper();
|
||||
} else {
|
||||
this.consumerWrapper = new SingleConsumerWrapper();
|
||||
}
|
||||
log.debug("[{}] Initialized consumer for queue: {}", queueName, config);
|
||||
}
|
||||
|
||||
public void update(C config) {
|
||||
addTask(QueueConsumerManagerTask.configUpdate(config));
|
||||
}
|
||||
|
||||
public void update(Set<TopicPartitionInfo> partitions) {
|
||||
addTask(QueueConsumerManagerTask.partitionChange(partitions));
|
||||
}
|
||||
|
||||
protected void addTask(QueueConsumerManagerTask todo) {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
tasks.add(todo);
|
||||
log.info("[{}] Added task: {}", queueName, todo);
|
||||
tryProcessTasks();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void tryProcessTasks() {
|
||||
taskExecutor.submit(() -> {
|
||||
if (lock.tryLock()) {
|
||||
try {
|
||||
C newConfig = null;
|
||||
Set<TopicPartitionInfo> newPartitions = null;
|
||||
while (!stopped) {
|
||||
QueueConsumerManagerTask task = tasks.poll();
|
||||
if (task == null) {
|
||||
break;
|
||||
}
|
||||
log.info("[{}] Processing task: {}", queueName, task);
|
||||
|
||||
if (task.getEvent() == QueueEvent.PARTITION_CHANGE) {
|
||||
newPartitions = task.getPartitions();
|
||||
} else if (task.getEvent() == QueueEvent.CONFIG_UPDATE) {
|
||||
newConfig = (C) task.getConfig();
|
||||
} else {
|
||||
processTask(task);
|
||||
}
|
||||
}
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
if (newConfig != null) {
|
||||
doUpdate(newConfig);
|
||||
}
|
||||
if (newPartitions != null) {
|
||||
doUpdate(newPartitions);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[{}] Failed to process tasks", queueName, e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
log.trace("[{}] Failed to acquire lock", queueName);
|
||||
scheduler.schedule(this::tryProcessTasks, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void processTask(QueueConsumerManagerTask task) {
|
||||
}
|
||||
|
||||
private void doUpdate(C newConfig) {
|
||||
log.info("[{}] Processing queue update: {}", queueName, newConfig);
|
||||
var oldConfig = this.config;
|
||||
this.config = newConfig;
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("[{}] Old queue configuration: {}", queueName, oldConfig);
|
||||
log.trace("[{}] New queue configuration: {}", queueName, newConfig);
|
||||
}
|
||||
|
||||
if (oldConfig == null) {
|
||||
init(config);
|
||||
} else if (newConfig.isConsumerPerPartition() != oldConfig.isConsumerPerPartition()) {
|
||||
consumerWrapper.getConsumers().forEach(QueueConsumerTask::initiateStop);
|
||||
consumerWrapper.getConsumers().forEach(QueueConsumerTask::awaitCompletion);
|
||||
|
||||
init(config);
|
||||
if (partitions != null) {
|
||||
doUpdate(partitions);
|
||||
}
|
||||
} else {
|
||||
log.trace("[{}] Silently applied new config, because consumer-per-partition not changed", queueName);
|
||||
}
|
||||
}
|
||||
|
||||
private void doUpdate(Set<TopicPartitionInfo> partitions) {
|
||||
this.partitions = partitions;
|
||||
consumerWrapper.updatePartitions(partitions);
|
||||
}
|
||||
|
||||
private void launchConsumer(QueueConsumerTask<M> consumerTask) {
|
||||
log.info("[{}] Launching consumer", consumerTask.getKey());
|
||||
Future<?> consumerLoop = consumerExecutor.submit(() -> {
|
||||
JCPPThreadFactory.updateCurrentThreadName(consumerTask.getKey().toString());
|
||||
try {
|
||||
consumerLoop(consumerTask.getConsumer());
|
||||
} catch (Throwable e) {
|
||||
log.error("Failure in consumer loop", e);
|
||||
}
|
||||
log.info("[{}] Consumer stopped", consumerTask.getKey());
|
||||
});
|
||||
consumerTask.setTask(consumerLoop);
|
||||
}
|
||||
|
||||
private void consumerLoop(QueueConsumer<M> consumer) {
|
||||
while (!stopped && !consumer.isStopped()) {
|
||||
try {
|
||||
List<M> msgs = consumer.poll(config.getPollInterval());
|
||||
if (msgs.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
processMsgs(msgs, consumer, config);
|
||||
} catch (Exception e) {
|
||||
if (!consumer.isStopped()) {
|
||||
log.warn("Failed to process messages from queue", e);
|
||||
try {
|
||||
Thread.sleep(config.getPollInterval());
|
||||
} catch (InterruptedException e2) {
|
||||
log.trace("Failed to wait until the server has capacity to handle new requests", e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (consumer.isStopped()) {
|
||||
consumer.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processMsgs(List<M> msgs, QueueConsumer<M> consumer, C config) throws Exception {
|
||||
msgPackProcessor.process(msgs, consumer, config);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
log.debug("[{}] Stopping consumers", queueName);
|
||||
consumerWrapper.getConsumers().forEach(QueueConsumerTask::initiateStop);
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
public void awaitStop() {
|
||||
log.debug("[{}] Waiting for consumers to stop", queueName);
|
||||
consumerWrapper.getConsumers().forEach(QueueConsumerTask::awaitCompletion);
|
||||
log.debug("[{}] Unsubscribed and stopped consumers", queueName);
|
||||
}
|
||||
|
||||
private static String partitionsToString(Collection<TopicPartitionInfo> partitions) {
|
||||
return partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.joining(", ", "[", "]"));
|
||||
}
|
||||
|
||||
public interface MsgPackProcessor<M extends QueueMsg, C extends QueueConfig> {
|
||||
void process(List<M> msgs, QueueConsumer<M> consumer, C config) throws Exception;
|
||||
}
|
||||
|
||||
public interface ConsumerWrapper<M extends QueueMsg> {
|
||||
|
||||
void updatePartitions(Set<TopicPartitionInfo> partitions);
|
||||
|
||||
Collection<QueueConsumerTask<M>> getConsumers();
|
||||
|
||||
}
|
||||
|
||||
class ConsumerPerPartitionWrapper implements ConsumerWrapper<M> {
|
||||
private final Map<TopicPartitionInfo, QueueConsumerTask<M>> consumers = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void updatePartitions(Set<TopicPartitionInfo> partitions) {
|
||||
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions);
|
||||
addedPartitions.removeAll(consumers.keySet());
|
||||
|
||||
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(consumers.keySet());
|
||||
removedPartitions.removeAll(partitions);
|
||||
log.info("[{}] Added partitions: {}, removed partitions: {}", queueName, partitionsToString(addedPartitions), partitionsToString(removedPartitions));
|
||||
|
||||
removedPartitions.forEach((tpi) -> consumers.get(tpi).initiateStop());
|
||||
removedPartitions.forEach((tpi) -> consumers.remove(tpi).awaitCompletion());
|
||||
|
||||
addedPartitions.forEach((tpi) -> {
|
||||
Integer partitionId = tpi.getPartition().orElse(-1);
|
||||
String key = queueName + "-" + partitionId;
|
||||
QueueConsumerTask<M> consumer = new QueueConsumerTask<>(key, () -> consumerCreator.apply(config, partitionId));
|
||||
consumers.put(tpi, consumer);
|
||||
consumer.subscribe(Set.of(tpi));
|
||||
launchConsumer(consumer);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<QueueConsumerTask<M>> getConsumers() {
|
||||
return consumers.values();
|
||||
}
|
||||
}
|
||||
|
||||
class SingleConsumerWrapper implements ConsumerWrapper<M> {
|
||||
private QueueConsumerTask<M> consumer;
|
||||
|
||||
@Override
|
||||
public void updatePartitions(Set<TopicPartitionInfo> partitions) {
|
||||
log.info("[{}] New partitions: {}", queueName, partitionsToString(partitions));
|
||||
if (partitions.isEmpty()) {
|
||||
if (consumer != null && consumer.isRunning()) {
|
||||
consumer.initiateStop();
|
||||
consumer.awaitCompletion();
|
||||
}
|
||||
consumer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (consumer == null) {
|
||||
consumer = new QueueConsumerTask<>(queueName, () -> consumerCreator.apply(config, null)); // no partitionId passed
|
||||
}
|
||||
consumer.subscribe(partitions);
|
||||
if (!consumer.isRunning()) {
|
||||
launchConsumer(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<QueueConsumerTask<M>> getConsumers() {
|
||||
if (consumer == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return List.of(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import sanbing.jcpp.infrastructure.queue.common.QueueConfig;
|
||||
import sanbing.jcpp.infrastructure.queue.common.TopicPartitionInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
public class QueueConsumerManagerTask {
|
||||
|
||||
private final QueueEvent event;
|
||||
private QueueConfig config;
|
||||
private Set<TopicPartitionInfo> partitions;
|
||||
private boolean drainQueue;
|
||||
|
||||
public static QueueConsumerManagerTask delete(boolean drainQueue) {
|
||||
return new QueueConsumerManagerTask(QueueEvent.DELETE, null, null, drainQueue);
|
||||
}
|
||||
|
||||
public static QueueConsumerManagerTask configUpdate(QueueConfig config) {
|
||||
return new QueueConsumerManagerTask(QueueEvent.CONFIG_UPDATE, config, null, false);
|
||||
}
|
||||
|
||||
public static QueueConsumerManagerTask partitionChange(Set<TopicPartitionInfo> partitions) {
|
||||
return new QueueConsumerManagerTask(QueueEvent.PARTITION_CHANGE, null, partitions, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.queue.QueueConsumer;
|
||||
import sanbing.jcpp.infrastructure.queue.QueueMsg;
|
||||
import sanbing.jcpp.infrastructure.queue.common.TopicPartitionInfo;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Slf4j
|
||||
public class QueueConsumerTask<M extends QueueMsg> {
|
||||
|
||||
@Getter
|
||||
private final Object key;
|
||||
private volatile QueueConsumer<M> consumer;
|
||||
private volatile Supplier<QueueConsumer<M>> consumerSupplier;
|
||||
|
||||
@Setter
|
||||
private Future<?> task;
|
||||
|
||||
public QueueConsumerTask(Object key, Supplier<QueueConsumer<M>> consumerSupplier) {
|
||||
this.key = key;
|
||||
this.consumer = null;
|
||||
this.consumerSupplier = consumerSupplier;
|
||||
}
|
||||
|
||||
public QueueConsumer<M> getConsumer() {
|
||||
if (consumer == null) {
|
||||
synchronized (this) {
|
||||
if (consumer == null) {
|
||||
Objects.requireNonNull(consumerSupplier, "consumerSupplier for key [" + key + "] is null");
|
||||
consumer = consumerSupplier.get();
|
||||
Objects.requireNonNull(consumer, "consumer for key [" + key + "] is null");
|
||||
consumerSupplier = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumer;
|
||||
}
|
||||
|
||||
public void subscribe(Set<TopicPartitionInfo> partitions) {
|
||||
log.info("[{}] Subscribing to partitions: {}", key, partitions);
|
||||
getConsumer().subscribe(partitions);
|
||||
}
|
||||
|
||||
public void initiateStop() {
|
||||
log.debug("[{}] Initiating stop", key);
|
||||
getConsumer().stop();
|
||||
}
|
||||
|
||||
public void awaitCompletion() {
|
||||
log.trace("[{}] Awaiting finish", key);
|
||||
if (isRunning()) {
|
||||
try {
|
||||
task.get(30, TimeUnit.SECONDS);
|
||||
log.trace("[{}] Awaited finish", key);
|
||||
} catch (Exception e) {
|
||||
log.warn("[{}] Failed to await for consumer to stop", key, e);
|
||||
}
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return task != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public enum QueueEvent implements Serializable {
|
||||
|
||||
PARTITION_CHANGE, CONFIG_UPDATE, DELETE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* 抖音关注:程序员三丙
|
||||
* 知识星球:https://t.zsxq.com/j9b21
|
||||
*/
|
||||
package sanbing.jcpp.app.service.queue.consumer;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import sanbing.jcpp.app.service.PileProtocolService;
|
||||
import sanbing.jcpp.app.service.queue.AbstractConsumerService;
|
||||
import sanbing.jcpp.app.service.queue.AppConsumerStats;
|
||||
import sanbing.jcpp.app.service.queue.AppQueueConsumerManager;
|
||||
import sanbing.jcpp.infrastructure.queue.*;
|
||||
import sanbing.jcpp.infrastructure.queue.common.QueueConfig;
|
||||
import sanbing.jcpp.infrastructure.queue.common.TopicPartitionInfo;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.PartitionProvider;
|
||||
import sanbing.jcpp.infrastructure.queue.discovery.event.PartitionChangeEvent;
|
||||
import sanbing.jcpp.infrastructure.queue.processing.IdMsgPair;
|
||||
import sanbing.jcpp.infrastructure.queue.provider.AppQueueFactory;
|
||||
import sanbing.jcpp.infrastructure.stats.StatsFactory;
|
||||
import sanbing.jcpp.infrastructure.util.annotation.AppComponent;
|
||||
import sanbing.jcpp.infrastructure.util.codec.ByteUtil;
|
||||
import sanbing.jcpp.infrastructure.util.mdc.MDCUtils;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
|
||||
import sanbing.jcpp.infrastructure.util.trace.TracerRunnable;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.queue.common.QueueConstants.MSG_MD_PREFIX;
|
||||
import static sanbing.jcpp.infrastructure.queue.common.QueueConstants.MSG_MD_TS;
|
||||
import static sanbing.jcpp.infrastructure.util.trace.TracerContextUtil.JCPP_TRACER_ID;
|
||||
import static sanbing.jcpp.infrastructure.util.trace.TracerContextUtil.JCPP_TRACER_ORIGIN;
|
||||
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
*/
|
||||
@Service
|
||||
@AppComponent
|
||||
@Slf4j
|
||||
public class ProtocolUplinkConsumerService extends AbstractConsumerService implements ApplicationListener<PartitionChangeEvent> {
|
||||
|
||||
@Value("${queue.app.poll-interval}")
|
||||
private int pollInterval;
|
||||
@Value("${queue.app.pack-processing-timeout}")
|
||||
private long packProcessingTimeout;
|
||||
@Value("${queue.app.consumer-per-partition}")
|
||||
private boolean consumerPerPartition;
|
||||
@Value("${queue.app.stats.enabled}")
|
||||
private boolean statsEnabled;
|
||||
|
||||
private final PileProtocolService pileProtocolService;
|
||||
|
||||
private final AppQueueFactory appQueueFactory;
|
||||
|
||||
private AppQueueConsumerManager<ProtoQueueMsg<UplinkQueueMessage>, AppQueueConfig> appConsumer;
|
||||
|
||||
private final AppConsumerStats stats;
|
||||
|
||||
public ProtocolUplinkConsumerService(PartitionProvider partitionProvider,
|
||||
ApplicationEventPublisher eventPublisher,
|
||||
PileProtocolService pileProtocolService,
|
||||
AppQueueFactory appQueueFactory,
|
||||
StatsFactory statsFactory) {
|
||||
super(partitionProvider, eventPublisher);
|
||||
this.pileProtocolService = pileProtocolService;
|
||||
this.appQueueFactory = appQueueFactory;
|
||||
this.stats = new AppConsumerStats(statsFactory);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
super.init("jcpp-app");
|
||||
|
||||
log.info("Initializing Protocol Uplink Messages Queue Subscriptions.");
|
||||
|
||||
this.appConsumer = AppQueueConsumerManager.<ProtoQueueMsg<UplinkQueueMessage>, AppQueueConfig>builder()
|
||||
.queueName("protocol uplink")
|
||||
.config(AppQueueConfig.of(consumerPerPartition, pollInterval))
|
||||
.msgPackProcessor(this::processMsgs)
|
||||
.consumerCreator((config, partitionId) -> appQueueFactory.createProtocolUplinkMsgConsumer())
|
||||
.consumerExecutor(consumersExecutor)
|
||||
.scheduler(scheduler)
|
||||
.taskExecutor(mgmtExecutor)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void stopConsumers() {
|
||||
super.stopConsumers();
|
||||
appConsumer.stop();
|
||||
appConsumer.awaitStop();
|
||||
}
|
||||
|
||||
|
||||
@Scheduled(fixedDelayString = "${queue.app.stats.print-interval-ms}")
|
||||
public void printStats() {
|
||||
if (statsEnabled) {
|
||||
stats.printStats();
|
||||
stats.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void processMsgs(List<ProtoQueueMsg<UplinkQueueMessage>> msgs, QueueConsumer<ProtoQueueMsg<UplinkQueueMessage>> consumer, AppQueueConfig config) throws Exception {
|
||||
List<IdMsgPair<UplinkQueueMessage>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
|
||||
ConcurrentMap<UUID, ProtoQueueMsg<UplinkQueueMessage>> pendingMap = orderedMsgList.stream().collect(
|
||||
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
|
||||
CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
|
||||
PackProcessingContext<ProtoQueueMsg<UplinkQueueMessage>> ctx = new PackProcessingContext<>(
|
||||
processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
|
||||
PendingMsgHolder pendingMsgHolder = new PendingMsgHolder();
|
||||
Future<?> packSubmitFuture = consumersExecutor.submit(new TracerRunnable(() ->
|
||||
orderedMsgList.forEach((element) -> {
|
||||
UUID id = element.getUuid();
|
||||
ProtoQueueMsg<UplinkQueueMessage> msg = element.getMsg();
|
||||
tracer(msg);
|
||||
log.trace("[{}] Creating main callback for message: {}", id, msg.getValue());
|
||||
Callback callback = new PackCallback<>(id, ctx);
|
||||
try {
|
||||
UplinkQueueMessage uplinkQueueMsg = msg.getValue();
|
||||
pendingMsgHolder.setUplinkQueueMessage(uplinkQueueMsg);
|
||||
if (uplinkQueueMsg.hasLoginRequest()) {
|
||||
pileProtocolService.pileLogin(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasHeartBeatRequest()) {
|
||||
pileProtocolService.heartBeat(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasVerifyPricingRequest()) {
|
||||
pileProtocolService.verifyPricing(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasQueryPricingRequest()) {
|
||||
pileProtocolService.queryPricing(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasGunRunStatusProto()) {
|
||||
pileProtocolService.postGunRunStatus(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasChargingProgressProto()) {
|
||||
pileProtocolService.postChargingProgress(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasSetPricingResponse()) {
|
||||
pileProtocolService.onSetPricingResponse(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasRemoteStartChargingResponse()) {
|
||||
pileProtocolService.onRemoteStartChargingResponse(uplinkQueueMsg, callback);
|
||||
} else if (uplinkQueueMsg.hasRemoteStopChargingResponse()) {
|
||||
pileProtocolService.onRemoteStopChargingResponse(uplinkQueueMsg, callback);
|
||||
} else if(uplinkQueueMsg.hasTransactionRecord()){
|
||||
pileProtocolService.onTransactionRecord(uplinkQueueMsg, callback);
|
||||
}else {
|
||||
callback.onSuccess();
|
||||
}
|
||||
|
||||
if (statsEnabled) {
|
||||
stats.log(uplinkQueueMsg);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.warn("[{}] Failed to process message: {}", id, msg, e);
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}))
|
||||
);
|
||||
if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) {
|
||||
if (!packSubmitFuture.isDone()) {
|
||||
packSubmitFuture.cancel(true);
|
||||
UplinkQueueMessage lastSubmitMsg = pendingMsgHolder.getUplinkQueueMessage();
|
||||
log.warn("Timeout to process message: {}", lastSubmitMsg);
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue()));
|
||||
}
|
||||
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
|
||||
}
|
||||
consumer.commit();
|
||||
}
|
||||
|
||||
private void tracer(ProtoQueueMsg<UplinkQueueMessage> msg) {
|
||||
Optional.ofNullable(msg.getHeaders().get(MSG_MD_PREFIX + JCPP_TRACER_ID))
|
||||
.map(tracerId -> {
|
||||
String origin = null;
|
||||
byte[] tracerOrigin = msg.getHeaders().get(MSG_MD_PREFIX + JCPP_TRACER_ORIGIN);
|
||||
if (tracerOrigin != null) {
|
||||
origin = ByteUtil.bytesToString(tracerOrigin);
|
||||
}
|
||||
|
||||
long ts = System.currentTimeMillis();
|
||||
byte[] tracerTs = msg.getHeaders().get(MSG_MD_PREFIX + MSG_MD_TS);
|
||||
if (tracerTs != null) {
|
||||
ts = ByteUtil.bytesToLong(tracerTs);
|
||||
}
|
||||
|
||||
return TracerContextUtil.newTracer(ByteUtil.bytesToString(tracerId), origin, ts);
|
||||
})
|
||||
.orElseGet(TracerContextUtil::newTracer);
|
||||
|
||||
MDCUtils.recordTracer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMgmtThreadPoolSize() {
|
||||
return Math.max(Runtime.getRuntime().availableProcessors(), 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onJCPPApplicationEvent(PartitionChangeEvent event) {
|
||||
Set<TopicPartitionInfo> appPartitions = event.getAppPartitions();
|
||||
log.info("Subscribing to partitions: {}", appPartitions);
|
||||
appConsumer.update(appPartitions);
|
||||
}
|
||||
|
||||
@Data(staticConstructor = "of")
|
||||
public static class AppQueueConfig implements QueueConfig {
|
||||
private final boolean consumerPerPartition;
|
||||
private final int pollInterval;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private static class PendingMsgHolder {
|
||||
private volatile UplinkQueueMessage uplinkQueueMessage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user