* !44 comment
* !39 添加下行日志打印
* !36 扩展计价领域模型
* !35 webui 初步成型
* !34 webui 初步成型
This commit is contained in:
三丙
2025-09-09 08:23:59 +00:00
parent 921045af8f
commit 58580ca11e
372 changed files with 37900 additions and 1206 deletions

View File

@@ -33,6 +33,12 @@
<groupId>sanbing</groupId>
<artifactId>jcpp-app</artifactId>
</dependency>
<dependency>
<groupId>sanbing</groupId>
<artifactId>jcpp-web-ui</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>sanbing</groupId>
<artifactId>jcpp-protocol-yunkuaichong</artifactId>
@@ -54,24 +60,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
<layout>ZIP</layout>
<mainClass>sanbing.jcpp.JCPPServerApplication</mainClass>
<excludeDevtools>true</excludeDevtools>
<layers>
<enabled>true</enabled>
<configuration>${project.basedir}/src/layers.xml</configuration>
</layers>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/boot/layers/layers-2.7.xsd">
<application>
<into layer="spring-boot-loader">
<include>org/springframework/boot/loader/**</include>
</into>
<into layer="application" />
</application>
<dependencies>
<into layer="application">
<includeModuleDependencies />
</into>
<into layer="snapshot-dependencies">
<include>*:*:*SNAPSHOT</include>
</into>
<into layer="dependencies" />
</dependencies>
<layerOrder>
<layer>dependencies</layer>
<layer>spring-boot-loader</layer>
<layer>snapshot-dependencies</layer>
<layer>application</layer>
</layerOrder>
</layers>

View File

@@ -6,26 +6,34 @@
*/
package sanbing.jcpp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.Ordered;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import sanbing.jcpp.infrastructure.util.annotation.AfterStartUp;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* @author baigod
* @author 九筒
*/
@SpringBootApplication
@EnableAsync
@EnableScheduling
@Slf4j
public class JCPPServerApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "app-service";
private static long startTs;
public static void main(String[] args) {
startTs = System.currentTimeMillis();
new SpringApplicationBuilder(JCPPServerApplication.class).bannerMode(Banner.Mode.LOG).run(updateArguments(args));
}
@@ -38,4 +46,10 @@ public class JCPPServerApplication {
}
return args;
}
@AfterStartUp(order = Ordered.LOWEST_PRECEDENCE)
public void afterStartUp() {
long startupTimeMs = System.currentTimeMillis() - startTs;
log.info("Started JChargePointProtocol App Service in {} seconds", TimeUnit.MILLISECONDS.toSeconds(startupTimeMs));
}
}

View File

@@ -1,4 +1,4 @@
server:
server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
port: "${HTTP_BIND_PORT:8080}"
undertow:
@@ -16,6 +16,10 @@
options:
server:
record-request-start-time: true
servlet:
encoding:
charset: UTF-8
force: true
spring:
application:
@@ -29,6 +33,50 @@ spring:
leak-detection-threshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}"
maximum-pool-size: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:64}"
register-mbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}"
servlet:
multipart:
max-file-size: "${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE:50MB}"
max-request-size: "${SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE:50MB}"
main:
allow-circular-references: "true"
mvc:
pathmatch.matching-strategy: "ANT_PATH_MATCHER"
async.request-timeout: "${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:30000}"
cors:
mappings:
"[/api/**]":
allowed-origin-patterns: "*"
allowed-methods: "*"
allowed-headers: "*"
max-age: "1800"
allow-credentials: "true"
web:
resources:
chain:
compressed: "true"
strategy:
content:
enabled: "true"
security:
jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}"
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}"
tokenIssuer: "${JWT_TOKEN_ISSUER:sanbing.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:NUI3T1FCSVI0MFNweFAxRjEyUUJ1QkVPc3V0T1hEdUJON0hiZ2NydzdzN0RBSVZwTE9rMFFWeDVTRnZ6djMxSw==}"
# 安全设置配置
settings:
# 密码策略
passwordPolicy:
minimumLength: "${SECURITY_PASSWORD_POLICY_MINIMUM_LENGTH:6}"
maximumLength: "${SECURITY_PASSWORD_POLICY_MAXIMUM_LENGTH:72}"
minimumUppercaseLetters: "${SECURITY_PASSWORD_POLICY_MINIMUM_UPPERCASE_LETTERS:0}"
minimumLowercaseLetters: "${SECURITY_PASSWORD_POLICY_MINIMUM_LOWERCASE_LETTERS:0}"
minimumDigits: "${SECURITY_PASSWORD_POLICY_MINIMUM_DIGITS:0}"
minimumSpecialCharacters: "${SECURITY_PASSWORD_POLICY_MINIMUM_SPECIAL_CHARACTERS:0}"
allowWhitespaces: "${SECURITY_PASSWORD_POLICY_ALLOW_WHITESPACES:true}"
# 最大登录失败次数
maxFailedLoginAttempts: "${SECURITY_MAX_FAILED_LOGIN_ATTEMPTS:5}"
mybatis-plus:
type-handlers-package: sanbing.jcpp.app.dal.config.ibatis.typehandlers
@@ -111,17 +159,25 @@ queue:
stats:
enabled: "${QUEUE_APP_STATS_ENABLED:true}"
print-interval-ms: "${QUEUE_APP_STATS_PRINT_INTERVAL_MS:60000}"
timer-top-n: "${QUEUE_APP_STATS_TIMER_TOP_N:5}"
# 应用程序缓存配置
cache:
type: "${CACHE_TYPE:caffeine}" # caffeine or redis
maximumPoolSize: "${CACHE_MAXIMUM_POOL_SIZE:16}"
specs:
piles:
timeToLiveInMinutes: "${CACHE_SPECS_PILES_TTL:1440}"
maxSize: "${CACHE_SPECS_PILES_MAX_SIZE:100000}"
guns:
timeToLiveInMinutes: "${CACHE_SPECS_GUNS_TTL:1440}"
maxSize: "${CACHE_SPECS_GUNS_MAX_SIZE:1000000}"
pileSessions:
timeToLiveInMinutes: "${CACHE_SPECS_PILE_SESSIONS_TTL:1440}"
timeToLiveInMinutes: "${service.protocol.sessions.default-inactivity-timeout-in-sec}"
maxSize: "${CACHE_SPECS_PILE_SESSIONS_MAX_SIZE:100000}"
attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
redis:
connection:
@@ -133,7 +189,6 @@ redis:
clientName: "${REDIS_CLIENT_NAME:standalone}"
commandTimeout: "${REDIS_CLIENT_COMMAND_TIMEOUT:30000}"
shutdownTimeout: "${REDIS_CLIENT_SHUTDOWN_TIMEOUT:1000}"
readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}"
usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:true}"
cluster:
nodes: "${REDIS_NODES:redis-node-0:6379,redis-node-1:6379,redis-node-2:6379,redis-node-3:6379,redis-node-4:6379,redis-node-5:6379}"
@@ -160,6 +215,29 @@ redis:
blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}"
evictTtlInMs: "${REDIS_EVICT_TTL_MS:60000}"
# 数据库安装配置
install:
# 安装模式init-初始化数据库upgrade-升级disabled-不做任何操作
mode: "${INSTALL_MODE:disabled}"
# SQL相关配置
sql:
attributes:
# 批处理大小
batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:1000}"
# 批处理最大延迟(毫秒)
batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY:100}"
# 统计打印间隔(毫秒)
stats_print_interval_ms: "${SQL_ATTRIBUTES_STATS_PRINT_INTERVAL_MS:1000}"
# 批处理线程数
batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
# 值是否进行XSS验证
value_no_xss_validation: "${SQL_ATTRIBUTES_VALUE_NO_XSS_VALIDATION:false}"
# 批处理排序
batch_sort: "${SQL_BATCH_SORT:true}"
# 是否移除空字符
remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
service:
# 服务类型:纯协议解析前置 - protocol纯应用后端 - app单体服务(包含protocol和app) - monolith
type: "${SERVICE_TYPE:monolith}"

View File

@@ -1,4 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<configuration status="INFO" monitorInterval="30">
<properties>

View File

@@ -14,7 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
/**
* @author baigod
* @author 九筒
*/
@ActiveProfiles("test")
@SpringBootTest(classes = JCPPServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

View File

@@ -10,9 +10,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import sanbing.jcpp.AbstractTestBase;
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.app.dal.entity.Gun;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
@@ -22,10 +19,10 @@ import java.util.UUID;
import static sanbing.jcpp.app.dal.mapper.PileMapperIT.NORMAL_PILE_ID;
import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID;
import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID;
/**
* @author baigod
* @author 九筒
*/
class GunMapperIT extends AbstractTestBase {
static final UUID[] NORMAL_GUN_ID = new UUID[]{
@@ -61,11 +58,6 @@ class GunMapperIT extends AbstractTestBase {
.gunCode("202312120000" + new DecimalFormat("00").format(i + 1) + "-02")
.stationId(NORMAL_STATION_ID)
.pileId(pileId)
.ownerId(NORMAL_USER_ID)
.ownerType(OwnerTypeEnum.C)
.runStatus(GunRunStatusEnum.IDLE)
.runStatusUpdatedTime(LocalDateTime.now())
.optStatus(GunOptStatusEnum.AVAILABLE)
.build();
gunMapper.insertOrUpdate(gun);

View File

@@ -1,67 +0,0 @@
/**
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
package sanbing.jcpp.app.dal.mapper;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;
import sanbing.jcpp.AbstractTestBase;
import sanbing.jcpp.app.dal.config.ibatis.enums.OrderStatusEnum;
import sanbing.jcpp.app.dal.config.ibatis.enums.OrderTypeEnum;
import sanbing.jcpp.app.dal.entity.Order;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import static sanbing.jcpp.app.dal.mapper.GunMapperIT.NORMAL_GUN_ID;
import static sanbing.jcpp.app.dal.mapper.PileMapperIT.NORMAL_PILE_ID;
import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID;
import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID;
/**
* @author baigod
*/
class OrderMapperIT extends AbstractTestBase {
@Resource
OrderMapper orderMapper;
@Test
void testOrderMapper() {
orderMapper.delete(Wrappers.lambdaQuery());
Order order = Order.builder()
.id(UUID.randomUUID())
.internalOrderNo(IdUtil.getSnowflake(1, 1).nextIdStr())
.externalOrderNo(IdUtil.getSnowflake(1, 1).nextIdStr())
.pileOrderNo(RandomStringUtils.secure().nextNumeric(16))
.createdTime(LocalDateTime.now())
.additionalInfo(JacksonUtil.newObjectNode())
.updatedTime(LocalDateTime.now())
.cancelledTime(null)
.status(OrderStatusEnum.IN_CHARGING)
.type(OrderTypeEnum.CHARGE)
.creatorId(NORMAL_USER_ID)
.stationId(NORMAL_STATION_ID)
.pileId(NORMAL_PILE_ID[0])
.gunId(NORMAL_GUN_ID[0])
.plateNo("浙A88888")
.settlementAmount(new BigDecimal(100))
.settlementDetails(JacksonUtil.newObjectNode())
.electricityQuantity(new BigDecimal("100"))
.build();
orderMapper.insertOrUpdate(order);
log.info("{}", orderMapper.selectById(order.getId()));
}
}

View File

@@ -11,8 +11,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import sanbing.jcpp.AbstractTestBase;
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.app.dal.entity.Pile;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
@@ -25,10 +23,10 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID;
import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID;
/**
* @author baigod
* @author 九筒
*/
class PileMapperIT extends AbstractTestBase {
static final UUID[] NORMAL_PILE_ID = new UUID[]{
@@ -61,12 +59,9 @@ class PileMapperIT extends AbstractTestBase {
.pileCode("202312120000" + new DecimalFormat("00").format(i + 1))
.protocol("yunkuaichongV150")
.stationId(NORMAL_STATION_ID)
.ownerId(NORMAL_USER_ID)
.ownerType(OwnerTypeEnum.C)
.brand("星星")
.model("10A")
.manufacturer("星星")
.status(PileStatusEnum.IDLE)
.type(PileTypeEnum.AC)
.build();
@@ -90,12 +85,9 @@ class PileMapperIT extends AbstractTestBase {
.pileCode("20241015" + new DecimalFormat("000000").format(number.get()))
.protocol("yunkuaichongV150")
.stationId(NORMAL_STATION_ID)
.ownerId(NORMAL_USER_ID)
.ownerType(OwnerTypeEnum.C)
.brand("星星")
.model("10A")
.manufacturer("星星")
.status(PileStatusEnum.IDLE)
.type(PileTypeEnum.AC)
.build();
piles.add(pile);

View File

@@ -10,18 +10,16 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import sanbing.jcpp.AbstractTestBase;
import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum;
import sanbing.jcpp.app.dal.config.ibatis.enums.StationStatusEnum;
import sanbing.jcpp.app.dal.entity.Station;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
import java.time.LocalDateTime;
import java.util.UUID;
import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID;
/**
* @author baigod
* @author 九筒
*/
class StationMapperIT extends AbstractTestBase {
static final UUID NORMAL_STATION_ID = UUID.fromString("07d80c81-fe99-4a1f-a6aa-dc4d798b5626");
@@ -39,15 +37,12 @@ class StationMapperIT extends AbstractTestBase {
.additionalInfo(JacksonUtil.newObjectNode())
.stationName("三丙家专属充电站")
.stationCode("S20241001001")
.ownerId(NORMAL_USER_ID)
.longitude(120.107936F)
.latitude(30.267014F)
.ownerType(OwnerTypeEnum.C)
.province("浙江省")
.city("杭州市")
.county("西湖区")
.address("西溪路552-1号")
.status(StationStatusEnum.OPERATIONAL)
.build();
stationMapper.insertOrUpdate(station);

View File

@@ -10,15 +10,17 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import sanbing.jcpp.AbstractTestBase;
import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum;
import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum;
import sanbing.jcpp.app.dal.entity.User;
import sanbing.jcpp.app.service.security.model.UserCredentials;
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* @author baigod
* @author 九筒
*/
class UserMapperIT extends AbstractTestBase {
static final UUID NORMAL_USER_ID = UUID.fromString("21cbf909-a23a-4396-840a-f34061f59f95");
@@ -30,17 +32,33 @@ class UserMapperIT extends AbstractTestBase {
void curdTest() {
userMapper.delete(Wrappers.lambdaQuery());
// 创建UserCredentials对象
UserCredentials credentials = new UserCredentials();
credentials.setPassword("$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG"); // encoded "password123"
credentials.setEnabled(true);
credentials.setFailedLoginAttempts(0);
User user = User.builder()
.id(NORMAL_USER_ID)
.createdTime(LocalDateTime.now())
.additionalInfo(JacksonUtil.newObjectNode())
.status(UserStatusEnum.ENABLE)
.userName("sanbing")
.userCredentials(JacksonUtil.newObjectNode())
.userCredentials(credentials)
.authority(AuthorityEnum.SYS_ADMIN) // 添加权限字段
.version(1) // 添加版本字段
.build();
userMapper.insertOrUpdate(user);
log.info("{}", userMapper.selectById(NORMAL_USER_ID));
User savedUser = userMapper.selectById(NORMAL_USER_ID);
log.info("Saved user: {}", savedUser);
// 验证UserCredentials字段正确保存和读取
assert savedUser != null;
assert savedUser.getUserCredentials() != null;
assert savedUser.getUserCredentials().isEnabled();
assert "sanbing".equals(savedUser.getUserName());
assert AuthorityEnum.SYS_ADMIN.equals(savedUser.getAuthority());
}
}