diff --git a/.gitignore b/.gitignore
index a1c2a23..c7b1a41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,10 @@
# Compiled class file
*.class
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
# Log file
*.log
@@ -21,3 +26,31 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/README.md b/README.md
index b255b63..b7a044a 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,27 @@
# JChargePointProtocol
-#### 介绍
-JAVA 充电桩协议库
+###### 一个高性能、分布式、支持海量并发量的充电桩JAVA服务端,计划支持100种协议,为充电应用提供基础能力。
-#### 软件架构
-软件架构说明
+
+
+
+
+
+
+
+
-
-#### 安装教程
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 使用说明
-
-1. xxxx
-2. xxxx
-3. xxxx
+------------------------------
#### 参与贡献
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+1. Fork 本仓库
+2. 新建 Feat_xxx 分支
+3. 提交代码
+4. 加入社群
+5. 新建 Pull Request
-#### 特技
-
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+
\ No newline at end of file
diff --git a/docker/Dockerfile-App b/docker/Dockerfile-App
new file mode 100644
index 0000000..144da60
--- /dev/null
+++ b/docker/Dockerfile-App
@@ -0,0 +1,40 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/jcpp-base:latest AS base
+WORKDIR /app
+COPY . .
+RUN mvn -U -B -T 0.8C clean install -DskipTests
+
+#分层
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-jdk-slim-bullseye AS builder
+WORKDIR /app
+COPY --from=base /app/jcpp-app-bootstrap/target/application.jar application.jar
+RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
+
+# 执行
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-jdk-slim-bullseye
+WORKDIR /app
+COPY --from=builder /app/extracted/dependencies/ ./
+COPY --from=builder /app/extracted/spring-boot-loader/ ./
+COPY --from=builder /app/extracted/snapshot-dependencies/ ./
+COPY --from=builder /app/extracted/application/ ./
+COPY --from=base /app/jcpp-app-bootstrap/target/conf ./config
+COPY --from=base /app/docker/start.sh .
+
+RUN mkdir -p /var/log/sanbing && \
+ mkdir -p /var/log/sanbing/jcpp && \
+ mkdir -p /var/log/sanbing/accesslog && \
+ mkdir -p /var/log/sanbing/gc && \
+ mkdir -p /var/log/sanbing/heapdump && \
+ chmod 700 -R /var/log/*
+
+RUN chmod a+x *.sh && mv start.sh /usr/bin
+
+EXPOSE 8080 8080
+
+CMD ["start.sh"]
+
+
diff --git a/docker/Dockerfile-Base b/docker/Dockerfile-Base
new file mode 100644
index 0000000..770e4e3
--- /dev/null
+++ b/docker/Dockerfile-Base
@@ -0,0 +1,11 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/mvn:3.9.9-jdk21 AS base
+WORKDIR /app
+COPY . .
+RUN mvn -U -B -T 0.8C clean install -DskipTests
+RUN rm -rf /app
+
diff --git a/docker/Dockerfile-Protocol b/docker/Dockerfile-Protocol
new file mode 100644
index 0000000..7aa342a
--- /dev/null
+++ b/docker/Dockerfile-Protocol
@@ -0,0 +1,40 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/jcpp-base:latest AS base
+WORKDIR /app
+COPY . .
+RUN mvn -U -B -T 0.8C clean install -DskipTests
+
+#分层
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-jdk-slim-bullseye AS builder
+WORKDIR /app
+COPY --from=base /app/jcpp-protocol-bootstrap/target/application.jar application.jar
+RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
+
+# 执行
+FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-jdk-slim-bullseye
+WORKDIR /app
+COPY --from=builder /app/extracted/dependencies/ ./
+COPY --from=builder /app/extracted/spring-boot-loader/ ./
+COPY --from=builder /app/extracted/snapshot-dependencies/ ./
+COPY --from=builder /app/extracted/application/ ./
+COPY --from=base /app/jcpp-protocol-bootstrap/target/conf ./config
+COPY --from=base /app/docker/start.sh .
+
+RUN mkdir -p /var/log/sanbing && \
+ mkdir -p /var/log/sanbing/jcpp && \
+ mkdir -p /var/log/sanbing/accesslog && \
+ mkdir -p /var/log/sanbing/gc && \
+ mkdir -p /var/log/sanbing/heapdump && \
+ chmod 700 -R /var/log/*
+
+RUN chmod a+x *.sh && mv start.sh /usr/bin
+
+EXPOSE 8081 8081
+
+CMD ["start.sh"]
+
+
diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml
new file mode 100644
index 0000000..26b3478
--- /dev/null
+++ b/docker/docker-compose.kafka.yml
@@ -0,0 +1,57 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+networks:
+ sanbing-network:
+ driver: bridge
+ name: sanbing-network
+ ipam:
+ config:
+ - subnet: 10.10.0.0/24
+
+services:
+ zookeeper:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/zookeeper:3.9
+ restart: always
+ networks:
+ - sanbing-network
+ ports:
+ - "2181:2181"
+ environment:
+ ALLOW_ANONYMOUS_LOGIN: true
+ kafka:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/kafka:3.7.1
+ restart: always
+ depends_on:
+ - zookeeper
+ networks:
+ - sanbing-network
+ ports:
+ - "9092:9092"
+ env_file:
+ - kafka.env
+ kafka-exporter:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/kafka-exporter:latest
+ restart: always
+ depends_on:
+ - kafka
+ networks:
+ - sanbing-network
+ ports:
+ - "9308:9308"
+ command:
+ - '--kafka.server=kafka:9092'
+ # 切换示例项目的队列类型为kafka
+ example:
+ restart: always
+ image: example:latest
+ depends_on:
+ - kafka
+ networks:
+ - sanbing-network
+ ports:
+ - "8080:8080"
+ env_file:
+ - queue-kafka.env
diff --git a/docker/docker-compose.redis-cluster.yml b/docker/docker-compose.redis-cluster.yml
new file mode 100644
index 0000000..eea1dfe
--- /dev/null
+++ b/docker/docker-compose.redis-cluster.yml
@@ -0,0 +1,90 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+networks:
+ sanbing-network:
+ driver: bridge
+ name: sanbing-network
+ ipam:
+ config:
+ - subnet: 10.10.0.0/24
+
+services:
+# Redis cluster
+ redis-node-0:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+
+ redis-node-1:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ depends_on:
+ - redis-node-0
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+
+ redis-node-2:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ depends_on:
+ - redis-node-1
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+
+ redis-node-3:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ depends_on:
+ - redis-node-2
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+
+ redis-node-4:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ depends_on:
+ - redis-node-3
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+
+ redis-node-5:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-cluster:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ depends_on:
+ - redis-node-0
+ - redis-node-1
+ - redis-node-2
+ - redis-node-3
+ - redis-node-4
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
+ - 'REDISCLI_AUTH=sanbing'
+ - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5'
+ - 'REDIS_CLUSTER_REPLICAS=1'
+ - 'REDIS_CLUSTER_CREATOR=yes'
diff --git a/docker/docker-compose.redis-sentinel.yml b/docker/docker-compose.redis-sentinel.yml
new file mode 100644
index 0000000..d769b33
--- /dev/null
+++ b/docker/docker-compose.redis-sentinel.yml
@@ -0,0 +1,49 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+networks:
+ sanbing-network:
+ driver: bridge
+ name: sanbing-network
+ ipam:
+ config:
+ - subnet: 10.10.0.0/24
+
+services:
+ redis-master:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ environment:
+ - 'REDIS_REPLICATION_MODE=master'
+ - 'REDIS_PASSWORD=sanbing'
+
+ redis-slave:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ environment:
+ - 'REDIS_REPLICATION_MODE=slave'
+ - 'REDIS_MASTER_HOST=redis-master'
+ - 'REDIS_MASTER_PASSWORD=sanbing'
+ - 'REDIS_PASSWORD=sanbing'
+ depends_on:
+ - redis-master
+
+ redis-sentinel:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis-sentinel:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ environment:
+ - 'REDIS_MASTER_HOST=redis-master'
+ - 'REDIS_MASTER_SET=mymaster'
+ - 'REDIS_SENTINEL_PASSWORD=sanbing'
+ - 'REDIS_MASTER_PASSWORD=sanbing'
+ depends_on:
+ - redis-master
+ - redis-slave
diff --git a/docker/docker-compose.redis-standalone.yml b/docker/docker-compose.redis-standalone.yml
new file mode 100644
index 0000000..8f118a5
--- /dev/null
+++ b/docker/docker-compose.redis-standalone.yml
@@ -0,0 +1,23 @@
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+networks:
+ sanbing-network:
+ driver: bridge
+ name: sanbing-network
+ ipam:
+ config:
+ - subnet: 10.10.0.0/24
+
+services:
+ redis:
+ image: registry.cn-hangzhou.aliyuncs.com/sanbing/redis:7.4
+ restart: always
+ networks:
+ - sanbing-network
+ ports:
+ - '6379:6379'
+ environment:
+ - 'REDIS_PASSWORD=sanbing'
diff --git a/docker/kafka.env b/docker/kafka.env
new file mode 100644
index 0000000..08123fd
--- /dev/null
+++ b/docker/kafka.env
@@ -0,0 +1,14 @@
+KAFKA_CFG_NODE_ID=0
+ALLOW_PLAINTEXT_LISTENER=yes
+KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
+KAFKA_CFG_LISTENERS=INSIDE://:9093,OUTSIDE://:9092
+KAFKA_CFG_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092
+KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
+KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
+KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INSIDE
+KAFKA_CFG_LOG_RETENTION_BYTES=1073741824
+KAFKA_CFG_SEGMENT_BYTES=268435456
+KAFKA_CFG_LOG_RETENTION_MS=300000
+KAFKA_CFG_LOG_CLEANUP_POLICY=delete
+
+
diff --git a/docker/queue-kafka.env b/docker/queue-kafka.env
new file mode 100644
index 0000000..1294636
--- /dev/null
+++ b/docker/queue-kafka.env
@@ -0,0 +1,2 @@
+QUEUE_TYPE=kafka
+KAFKA_SERVERS=kafka:9092
diff --git a/docker/schema/schema-postgres.sql b/docker/schema/schema-postgres.sql
new file mode 100644
index 0000000..ee2c0be
--- /dev/null
+++ b/docker/schema/schema-postgres.sql
@@ -0,0 +1,121 @@
+--
+-- 抖音关注:程序员三丙
+-- 知识星球:https://t.zsxq.com/j9b21
+--
+
+CREATE TABLE IF NOT EXISTS jcpp_user
+(
+ id uuid not null
+ constraint owner_pkey
+ primary key,
+ created_time timestamp default CURRENT_TIMESTAMP not null,
+ additional_info jsonb,
+ status varchar(16) not null,
+ user_name varchar(255) not null,
+ user_credentials jsonb not null,
+ version int default 1
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_user_name
+ on jcpp_user (user_name);
+
+CREATE TABLE IF NOT EXISTS jcpp_station
+(
+ id uuid not null
+ constraint station_pkey
+ primary key,
+ created_time timestamp default CURRENT_TIMESTAMP not null,
+ additional_info jsonb,
+ station_name varchar(255) not null,
+ station_code varchar(255) not null,
+ owner_id uuid not null,
+ longitude double precision not null,
+ latitude double precision not null,
+ owner_type varchar(16) not null,
+ province varchar(255),
+ city varchar(255),
+ county varchar(255),
+ address varchar(255),
+ status varchar(16) not null,
+ version int default 1
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_station_code
+ on jcpp_station (station_code);
+
+CREATE TABLE IF NOT EXISTS jcpp_pile
+(
+ id uuid not null
+ constraint pile_pkey
+ primary key,
+ created_time timestamp default CURRENT_TIMESTAMP not null,
+ additional_info jsonb,
+ pile_name varchar(255) not null,
+ pile_code varchar(255) not null,
+ protocol varchar(255) not null,
+ station_id uuid not null,
+ owner_id uuid not null,
+ owner_type varchar(16) not null,
+ brand varchar(255),
+ model varchar(255),
+ manufacturer varchar(255),
+ status varchar(16) not null,
+ type varchar(16) not null,
+ version int default 1
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_pile_code
+ on jcpp_pile (pile_code);
+
+
+CREATE TABLE IF NOT EXISTS jcpp_gun
+(
+ id uuid not null
+ primary key,
+ created_time timestamp default CURRENT_TIMESTAMP not null,
+ additional_info varchar(255),
+ gun_no varchar(255) not null,
+ gun_name varchar(255) not null,
+ gun_code varchar(255) not null,
+ station_id uuid not null,
+ pile_id uuid not null,
+ owner_id uuid not null,
+ owner_type varchar(16) not null,
+ run_status varchar(16) not null,
+ run_status_updated_time timestamp not null,
+ opt_status varchar(16) not null,
+ version int default 1
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_gun_code
+ on jcpp_gun (gun_code);
+
+CREATE TABLE IF NOT EXISTS jcpp_order
+(
+ id uuid not null
+ primary key,
+ internal_order_no varchar(255) not null,
+ external_order_no varchar(255) not null,
+ pile_order_No varchar(255) not null,
+ created_time timestamp default CURRENT_TIMESTAMP not null,
+ additional_info jsonb,
+ updated_time timestamp,
+ cancelled_time timestamp,
+ status varchar(16) not null,
+ type varchar(16) not null,
+ creator_id uuid not null,
+ station_id uuid not null,
+ pile_id uuid not null,
+ gun_id uuid not null,
+ plate_no varchar(64),
+ settlement_amount bigint default 0 not null,
+ settlement_details jsonb,
+ electricity_quantity numeric(16, 9) default 0 not null
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_internal_order_no
+ on jcpp_order (internal_order_no);
+
+CREATE UNIQUE INDEX IF NOT EXISTS uni_external_order_no
+ on jcpp_order (external_order_no);
+
diff --git a/docker/start.sh b/docker/start.sh
new file mode 100644
index 0000000..33ea7fa
--- /dev/null
+++ b/docker/start.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 抖音关注:程序员三丙
+# 知识星球:https://t.zsxq.com/j9b21
+#
+
+echo "Starting Server ..."
+
+export JAVA_APP_OPTS="-XX:+UseContainerSupport -XX:InitialRAMPercentage=10 -XX:MaxRAMPercentage=70 \
+ -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/sanbing/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=10M \
+ -XX:+HeapDumpOnOutOfMemoryError \
+ -XX:HeapDumpPath=/var/log/sanbing/heapdump/ \
+ -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark \
+ -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10 \
+ -Xss512k -XX:MaxDirectMemorySize=128M -XX:G1ReservePercent=20 \
+ -XX:-OmitStackTraceInFastThrow \
+ -Dlogging.config=/app/config/log4j2.xml"
+
+#export JAVA_OPTS_EXTEND="-Xdebug -Xrunjdwp:transport=dt_socket,address=0.0.0.0:8000,server=y,suspend=n"
+
+exec java $JAVA_APP_OPTS $JAVA_OPTS_EXTEND $JAVA_OPTS -Dnetworkaddress.cache.ttl=60 -jar /app/application.jar
diff --git a/jcpp-app-bootstrap/pom.xml b/jcpp-app-bootstrap/pom.xml
new file mode 100644
index 0000000..1297db4
--- /dev/null
+++ b/jcpp-app-bootstrap/pom.xml
@@ -0,0 +1,84 @@
+
+
+
+
+ sanbing
+ jcpp-parent
+ 1.0.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ jcpp-app-bootstrap
+ jar
+ JChargePointProtocol Application Bootstrap Module
+ App引导程序
+
+
+ ${basedir}/..
+ 3.4.4
+
+
+
+
+ sanbing
+ jcpp-app
+
+
+ sanbing
+ jcpp-protocol-yunkuaichong
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
+ application
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ false
+ ZIP
+ sanbing.jcpp.JCPPServerApplication
+ true
+
+ true
+ ${project.basedir}/src/layers.xml
+
+
+
+
+
+ repackage
+ build-info
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
diff --git a/jcpp-app-bootstrap/src/layers.xml b/jcpp-app-bootstrap/src/layers.xml
new file mode 100644
index 0000000..ebf721a
--- /dev/null
+++ b/jcpp-app-bootstrap/src/layers.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ org/springframework/boot/loader/**
+
+
+
+
+
+
+
+
+ *:*:*SNAPSHOT
+
+
+
+
+ dependencies
+ spring-boot-loader
+ snapshot-dependencies
+ application
+
+
diff --git a/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java b/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java
new file mode 100644
index 0000000..4631858
--- /dev/null
+++ b/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java
@@ -0,0 +1,39 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp;
+
+import org.springframework.boot.Banner;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.util.Arrays;
+
+/**
+ * @author baigod
+ */
+@SpringBootApplication
+@EnableAsync
+@EnableScheduling
+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";
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(JCPPServerApplication.class).bannerMode(Banner.Mode.LOG).run(updateArguments(args));
+ }
+
+ private static String[] updateArguments(String[] args) {
+ if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) {
+ String[] modifiedArgs = new String[args.length + 1];
+ System.arraycopy(args, 0, modifiedArgs, 0, args.length);
+ modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM;
+ return modifiedArgs;
+ }
+ return args;
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/main/resources/app-service.yml b/jcpp-app-bootstrap/src/main/resources/app-service.yml
new file mode 100644
index 0000000..175b136
--- /dev/null
+++ b/jcpp-app-bootstrap/src/main/resources/app-service.yml
@@ -0,0 +1,214 @@
+server:
+ address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
+ port: "${HTTP_BIND_PORT:8080}"
+ undertow:
+ buffer-size: "${SERVER_UNDERTOW_BUFFER_SIZE:16384}"
+ directBuffers: "${SERVER_UNDERTOW_DIRECT_BUFFERS:true}"
+ threads:
+ io: "${SERVER_UNDERTOW_THREADS_IO:4}"
+ worker: "${SERVER_UNDERTOW_THREADS_WORKER:128}"
+ max-http-post-size: "${SERVER_UNDERTOW_MAX_HTTP_POST_SIZE:10MB}"
+ no-request-timeout: "${SERVER_UNDERTOW_NO_REQUEST_TIMEOUT:10000}"
+ accesslog:
+ enabled: true
+ pattern: "%t %a %r %s (%D ms)"
+ dir: /var/log/sanbing/accesslog
+ options:
+ server:
+ record-request-start-time: true
+
+spring:
+ application:
+ name: "${SPRING_APPLICATION_NAME:java-charge-point-server}"
+ datasource:
+ driver-class-name: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
+ url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://10.102.12.102:30135/jcpp}"
+ username: "${SPRING_DATASOURCE_USERNAME:postgres}"
+ password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
+ hikari:
+ leak-detection-threshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}"
+ maximum-pool-size: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}"
+ register-mbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}"
+
+mybatis-plus:
+ type-handlers-package: sanbing.jcpp.app.dal.config.ibatis.typehandlers
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: '${METRICS_ENDPOINTS_EXPOSE:prometheus,health}'
+ endpoint:
+ health:
+ show-details: always
+
+metrics:
+ enabled: "${METRICS_ENABLED:true}"
+ timer:
+ percentiles: "${METRICS_TIMER_PERCENTILES:0.5}"
+
+# 应用程序服务注册中心配置
+zk:
+ enabled: "${ZOOKEEPER_ENABLED:true}"
+ url: "${ZOOKEEPER_URL:zookeeper:2181}"
+ retry-interval-ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}"
+ connection-timeout-ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}"
+ session-timeout-ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}"
+ zk-dir: "${ZOOKEEPER_NODES_DIR:/jcpp}"
+ recalculate-delay: "${ZOOKEEPER_RECALCULATE_DELAY_MS:0}"
+
+# 队列配置
+queue:
+ # 可选 kafka、memory
+ type: "${QUEUE_TYPE:memory}"
+ partitions:
+ hash_function_name: "${QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
+ in_memory:
+ stats:
+ print-interval-ms: "${QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
+ kafka:
+ bootstrap-servers: "${KAFKA_SERVERS:kafka:9092}"
+ ssl:
+ enabled: "${KAFKA_SSL_ENABLED:false}"
+ truststore-location: "${KAFKA_SSL_TRUSTSTORE_LOCATION:}"
+ truststore-password: "${KAFKA_SSL_TRUSTSTORE_PASSWORD:}"
+ keystore-location: "${KAFKA_SSL_KEYSTORE_LOCATION:}"
+ keystore-password: "${KAFKA_SSL_KEYSTORE_PASSWORD:}"
+ key-password: "${KAFKA_SSL_KEY_PASSWORD:}"
+ acks: "${KAFKA_ACKS:1}"
+ retries: "${KAFKA_RETRIES:1}"
+ compression-type: "${KAFKA_COMPRESSION_TYPE:lz4}" # none, gzip, snappy, lz4, zstd
+ batch-size: "${KAFKA_BATCH_SIZE:1048576}"
+ linger-ms: "${KAFKA_LINGER_MS:1}"
+ max-request-size: "${KAFKA_MAX_REQUEST_SIZE:1048576}"
+ max-in-flight-requests-per-connection: "${KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}"
+ buffer-memory: "${BUFFER_MEMORY:33554432}"
+ replication-factor: "${QUEUE_KAFKA_REPLICATION_FACTOR:1}"
+ max-poll-interval-ms: "${QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}"
+ max-poll-records: "${QUEUE_KAFKA_MAX_POLL_RECORDS:10240}"
+ max-partition-fetch-bytes: "${QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
+ fetch-max-bytes: "${QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
+ request-timeout-ms: "${QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}"
+ session-timeout-ms: "${QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}"
+ auto-offset-reset: "${QUEUE_KAFKA_AUTO_OFFSET_RESET:earliest}"
+ other-inline: "${QUEUE_KAFKA_OTHER_PROPERTIES:}"
+ topic-properties:
+ app: "${QUEUE_KAFKA_APP_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
+ consumer-stats:
+ enabled: "${QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
+ print-interval-ms: "${QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}"
+ kafka-response-timeout-ms: "${QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}"
+ app:
+ topic: "${QUEUE_APP_TOPIC:protocol_uplink}"
+ poll-interval: "${QUEUE_APP_POLL_INTERVAL_MS:5}"
+ pack-processing-timeout: "${QUEUE_APP_PACK_PROCESSING_TIMEOUT_MS:2000}"
+ consumer-per-partition: "${QUEUE_APP_CONSUMER_PER_PARTITION:true}"
+ partitions: "${QUEUE_APP_PARTITIONS:10}"
+ # 可选 protobuf(推荐)、json,需要跟..forwarder.kafka.encoder保持一致
+ decoder: "${QUEUE_APP_DECODER:protobuf}"
+ stats:
+ enabled: "${QUEUE_APP_STATS_ENABLED:true}"
+ print-interval-ms: "${QUEUE_APP_STATS_PRINT_INTERVAL_MS:60000}"
+
+# 应用程序缓存配置
+cache:
+ type: "${CACHE_TYPE:caffeine}" # caffeine or redis
+ specs:
+ piles:
+ timeToLiveInMinutes: "${CACHE_SPECS_PILES_TTL:15}"
+ maxSize: "${CACHE_SPECS_PILES_MAX_SIZE:1000}"
+ pileSessions:
+ timeToLiveInMinutes: "${CACHE_SPECS_PILE_SESSIONS_TTL:1440}"
+ maxSize: "${CACHE_SPECS_PILE_SESSIONS_MAX_SIZE:100000}"
+
+redis:
+ connection:
+ type: "${REDIS_CONNECTION_TYPE:standalone}"
+ standalone:
+ host: "${REDIS_HOST:redis}"
+ port: "${REDIS_PORT:6379}"
+ useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}"
+ 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:false}"
+ 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}"
+ max-redirects: "${REDIS_MAX_REDIRECTS:12}"
+ useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:false}"
+ sentinel:
+ master: "${REDIS_MASTER:mymaster}"
+ sentinels: "${REDIS_SENTINELS:redis-sentinel:26379}"
+ password: "${REDIS_SENTINEL_PASSWORD:sanbing}"
+ useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:false}"
+ db: "${REDIS_DB:0}"
+ password: "${REDIS_PASSWORD:sanbing}"
+ pool_config:
+ maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}"
+ maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}"
+ minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}"
+ testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:false}"
+ testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:false}"
+ testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}"
+ minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}"
+ evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}"
+ maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}"
+ numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}"
+ blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}"
+ evictTtlInMs: "${REDIS_EVICT_TTL_MS:60000}"
+
+service:
+ # 服务类型:纯协议解析前置 - protocol,纯应用后端 - app,单体服务(包含protocol和app) - monolith
+ type: "${SERVICE_TYPE:monolith}"
+ # 可自定义的服务ID,如果不指定,则默认为HOSTNAME
+ id: "${SERVICE_ID:}"
+ protocols:
+ sessions:
+ default-inactivity-timeout-in-sec: "${PROTOCOLS_SESSIONS_DEFAULT_INACTIVITY_TIMEOUT_IN_SEC:600}"
+ default-state-check-interval-in-sec: "${PROTOCOLS_SESSIONS_DEFAULT_STATE_CHECK_INTERVAL_IN_SEC:60}"
+ yunkuaichongV150:
+ enabled: "${PROTOCOLS_YUNKUAICHONGV150_ENABLED:true}"
+ listener:
+ tcp:
+ bind-address: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BIND_ADDRESS:0.0.0.0}"
+ bind-port: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BIND_PORT:38001}"
+ boss-group-thread_count: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_BOSS_GROUP_THREADS:4}"
+ worker-group-thread-count: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_WORKER_GROUP_THREADS:16}"
+ so-keep-alive: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_KEEPALIVE:true}"
+ so-backlog: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_BACKLOG:128}"
+ so-rcvbuf: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_RCVBUF:65536}"
+ so-sndbuf: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_SO_SNDBUF:65536}"
+ nodelay: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_NODELAY:true}"
+ handler:
+ idle-timeout-seconds: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_HANDLER_IDLE_TIMEOUT_SECONDS:600}"
+ max_connections: "${PROTOCOLS_YUNKUAICHONGV150_LISTENER_TCP_HANDLER_MAX_CONNECTIONS:100000}"
+ # 默认为二进制类型的拆包器
+ # 可选JSON类型的拆包器 "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:JSON}"
+ # 可选纯文本类型的拆包器 "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:TEXT;maxFrameLength:128;stripDelimiter:true;messageSeparator:null;charsetName:UTF-8}"
+ configuration: "${PROTOCOLS_YUNKUAICHONGV150_NETTY_HANDLER_BINARY_CONFIGURATION:type:BINARY;decoder:sanbing.jcpp.protocol.listener.tcp.decoder.JCPPLengthFieldBasedFrameDecoder;byteOrder:LITTLE_ENDIAN;head:68;lengthFieldOffset:1;lengthFieldLength:1;lengthAdjustment:2;initialBytesToStrip:0}"
+ forwarder:
+ # 如果是单体服务,可选kafka、memory,未来计划扩展RocketMQ, GRpc、REST
+ type: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_TYPE:memory}"
+ memory:
+ topic: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_MEMORY_TOPIC:protocol_uplink}"
+ kafka:
+ topic: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_TOPIC:protocol_uplink}"
+ jcpp-partition: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_JCPP_PARTITION:true}" # 是否利用JCPP的分片框架
+ # 以下配置只有在service.type为protocol时且jcpp-partition为false时才生效
+ bootstrap-servers: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_SERVERS:10.102.12.102:9092}"
+ acks: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_ACKS:1}"
+ # # 可选 protobuf(推荐)、json
+ encoder: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_ENCODER:protobuf}"
+ retries: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_RETRIES:1}"
+ compression-type: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_COMPRESSION_TYPE:lz4}" # none, gzip, snappy, lz4, zstd
+ batch-size: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_BATCH_SIZE:16384}"
+ linger-ms: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_KAFKA_LINGER_MS:0}"
+ buffer-memory: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_BUFFER_MEMORY:33554432}"
+ other-properties: "${PROTOCOLS_YUNKUAICHONGV150_FORWARD_QUEUE_KAFKA_OTHER_PROPERTIES:}"
+
+thread-pool:
+ sharding:
+ hash_function_name: "${THREAD_POOL_SHARDING_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
+ parallelism: "${THREAD_POOL_SHARDING_PARALLELISM:128}"
+ stats-print-interval-ms: "${THREAD_POOL_SHARDING_STATS_PRINT_INTERVAL_MS:10000}"
diff --git a/jcpp-app-bootstrap/src/main/resources/banner.txt b/jcpp-app-bootstrap/src/main/resources/banner.txt
new file mode 100644
index 0000000..0c37cd9
--- /dev/null
+++ b/jcpp-app-bootstrap/src/main/resources/banner.txt
@@ -0,0 +1,12 @@
+
+ ___ ________ ________ ________
+ |\ \|\ ____\|\ __ \|\ __ \
+ \ \ \ \ \___|\ \ \|\ \ \ \|\ \
+ __ \ \ \ \ \ \ \ ____\ \ ____\
+|\ \\_\ \ \ \____\ \ \___|\ \ \___|
+\ \________\ \_______\ \__\ \ \__\
+ \|________|\|_______|\|__| \|__|
+
+===================================================
+:: ${application.title} :: ${application.formatted-version}
+===================================================
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/main/resources/log4j2.xml b/jcpp-app-bootstrap/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..082044b
--- /dev/null
+++ b/jcpp-app-bootstrap/src/main/resources/log4j2.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ /var/log/sanbing/jcpp
+ %d{yyyy-MM-dd HH:mm:ss:SSS} [%X{TRACE_ID}] [%t] %p %c{1} %m%n%throwable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java
new file mode 100644
index 0000000..42f038e
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java
@@ -0,0 +1,27 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * @author baigod
+ */
+@ActiveProfiles("test")
+@SpringBootTest(classes = JCPPServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class AbstractTestBase {
+
+ static {
+ System.setProperty("spring.config.name", "app-service");
+ }
+
+ protected final Logger log = LoggerFactory.getLogger(this.getClass());
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperTest.java
new file mode 100644
index 0000000..592c781
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperTest.java
@@ -0,0 +1,72 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.app.dal.mapper;
+
+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;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+import static sanbing.jcpp.app.dal.mapper.PileMapperTest.NORMAL_PILE_ID;
+import static sanbing.jcpp.app.dal.mapper.StationMapperTest.NORMAL_STATION_ID;
+import static sanbing.jcpp.app.dal.mapper.UserMapperTest.NORMAL_USER_ID;
+
+/**
+ * @author baigod
+ */
+public class GunMapperTest extends AbstractTestBase {
+ static final UUID[] NORMAL_GUN_ID = new UUID[]{
+ UUID.fromString("8f1ffb5b-e536-4f2b-8cd0-31f7d0348a44"),
+ UUID.fromString("ae256617-b747-4110-b27a-00773e03bed1"),
+ UUID.fromString("d15dbb29-ea2f-4094-b448-dff853e9275f"),
+ UUID.fromString("b4a2de24-d7ff-4828-a0d8-2429a6253f9c"),
+ UUID.fromString("f505f7e2-9e1c-4251-8f7f-9a8eae84372a"),
+ UUID.fromString("0c5bab7b-786b-4e05-ab26-618c3f5a6086"),
+ UUID.fromString("2db4ad92-e353-4ac2-a2b0-942cb778eca6"),
+ UUID.fromString("203833e7-0a44-4f1c-935e-cd43e6dbbf46"),
+ UUID.fromString("3f3a61e9-de55-4177-9b4e-3a1d8c529890"),
+ UUID.fromString("cf1a8970-5aa9-4636-a76e-d6bcf98b4a07")
+ };
+ @Resource
+ GunMapper gunMapper;
+
+ @Test
+ void curdTest() {
+ gunMapper.delete(Wrappers.lambdaQuery());
+
+ for (int i = 0; i < NORMAL_PILE_ID.length; i++) {
+ UUID pileId = NORMAL_PILE_ID[i];
+ UUID gunId = NORMAL_GUN_ID[i];
+ Gun gun = Gun.builder()
+ .id(gunId)
+ .createdTime(LocalDateTime.now())
+ .additionalInfo(JacksonUtil.newObjectNode())
+ .gunNo("01")
+ .gunName("三丙的1号枪")
+ .gunCode("20231212000001-" + (i + 1))
+ .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);
+
+ log.info("{}", gunMapper.selectById(gunId));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperTest.java
new file mode 100644
index 0000000..eb2a24f
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperTest.java
@@ -0,0 +1,66 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.app.dal.mapper;
+
+import cn.hutool.core.math.Money;
+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.GunMapperTest.NORMAL_GUN_ID;
+import static sanbing.jcpp.app.dal.mapper.PileMapperTest.NORMAL_PILE_ID;
+import static sanbing.jcpp.app.dal.mapper.StationMapperTest.NORMAL_STATION_ID;
+import static sanbing.jcpp.app.dal.mapper.UserMapperTest.NORMAL_USER_ID;
+
+/**
+ * @author baigod
+ */
+public class OrderMapperTest 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.randomNumeric(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 Money(100D).getCent())
+ .settlementDetails(JacksonUtil.newObjectNode())
+ .electricityQuantity(new BigDecimal("100"))
+ .build();
+
+ orderMapper.insertOrUpdate(order);
+
+ log.info("{}", orderMapper.selectById(order.getId()));
+
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperTest.java
new file mode 100644
index 0000000..b3afb7b
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperTest.java
@@ -0,0 +1,74 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.app.dal.mapper;
+
+
+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;
+
+import java.text.DecimalFormat;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+import static sanbing.jcpp.app.dal.mapper.StationMapperTest.NORMAL_STATION_ID;
+import static sanbing.jcpp.app.dal.mapper.UserMapperTest.NORMAL_USER_ID;
+
+/**
+ * @author baigod
+ */
+public class PileMapperTest extends AbstractTestBase {
+ static final UUID[] NORMAL_PILE_ID = new UUID[]{
+ UUID.fromString("fd7b3f60-db6c-4347-bff3-3c922985b95c"),
+ UUID.fromString("fa621927-6458-4e09-9666-99c52230db2b"),
+ UUID.fromString("afec0b0a-ad82-4923-97da-70e4a5d5e2c6"),
+ UUID.fromString("3e45ae30-2848-4d5a-a7b8-bd8504a6713d"),
+ UUID.fromString("349ff65e-ce8e-435a-928b-52fdef2828f2"),
+ UUID.fromString("e60d5b2d-8014-4f8f-b828-e207e6cf4a8f"),
+ UUID.fromString("8f010829-b505-4e57-8b93-6bdf981ac4e1"),
+ UUID.fromString("081842e2-9e74-4abb-aeab-b2cbfeb7a335"),
+ UUID.fromString("f04cf40a-0fbe-40f7-a07c-5b663ad68e98"),
+ UUID.fromString("ec522751-e1d3-4117-a887-3bdae7892369")
+ };
+
+ @Resource
+ PileMapper pileMapper;
+
+ @Test
+ void curdTest() {
+ pileMapper.delete(Wrappers.lambdaQuery());
+
+ for (int i = 0; i < 10; i++) {
+ UUID pileId = NORMAL_PILE_ID[i];
+ Pile pile = Pile.builder()
+ .id(pileId)
+ .createdTime(LocalDateTime.now())
+ .additionalInfo(JacksonUtil.newObjectNode())
+ .pileName(String.format("三丙家的%d号充电桩", i + 1))
+ .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();
+
+ pileMapper.insertOrUpdate(pile);
+
+ log.info("{}", pileMapper.selectById(pileId));
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperTest.java
new file mode 100644
index 0000000..63a6f02
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperTest.java
@@ -0,0 +1,55 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.app.dal.mapper;
+
+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.UserMapperTest.NORMAL_USER_ID;
+
+/**
+ * @author baigod
+ */
+class StationMapperTest extends AbstractTestBase {
+ static final UUID NORMAL_STATION_ID = UUID.fromString("07d80c81-fe99-4a1f-a6aa-dc4d798b5626");
+
+ @Resource
+ StationMapper stationMapper;
+
+ @Test
+ void curdTest() {
+ stationMapper.delete(Wrappers.lambdaQuery());
+
+ Station station = Station.builder()
+ .id(NORMAL_STATION_ID)
+ .createdTime(LocalDateTime.now())
+ .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);
+
+ log.info("{}", stationMapper.selectById(NORMAL_STATION_ID));
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperTest.java
new file mode 100644
index 0000000..be710a4
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperTest.java
@@ -0,0 +1,44 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.app.dal.mapper;
+
+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.UserStatusEnum;
+import sanbing.jcpp.app.dal.entity.User;
+import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * @author baigod
+ */
+class UserMapperTest extends AbstractTestBase {
+ static final UUID NORMAL_USER_ID = UUID.fromString("21cbf909-a23a-4396-840a-f34061f59f95");
+
+ @Resource
+ private UserMapper userMapper;
+
+ @Test
+ void curdTest() {
+ userMapper.delete(Wrappers.lambdaQuery());
+
+ User user = User.builder()
+ .id(NORMAL_USER_ID)
+ .createdTime(LocalDateTime.now())
+ .additionalInfo(JacksonUtil.newObjectNode())
+ .status(UserStatusEnum.ENABLE)
+ .userName("sanbing")
+ .userCredentials(JacksonUtil.newObjectNode())
+ .build();
+
+ userMapper.insertOrUpdate(user);
+
+ log.info("{}", userMapper.selectById(NORMAL_USER_ID));
+ }
+}
\ No newline at end of file
diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/infrastructure/cache/RedisCacheConfigurationTest.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/infrastructure/cache/RedisCacheConfigurationTest.java
new file mode 100644
index 0000000..1c41e34
--- /dev/null
+++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/infrastructure/cache/RedisCacheConfigurationTest.java
@@ -0,0 +1,98 @@
+/**
+ * 抖音关注:程序员三丙
+ * 知识星球:https://t.zsxq.com/j9b21
+ */
+package sanbing.jcpp.infrastructure.cache;
+
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.redis.core.*;
+import sanbing.jcpp.AbstractTestBase;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.IntStream;
+
+class RedisCacheConfigurationTest extends AbstractTestBase {
+
+ @Resource
+ RedisTemplate redisTemplate;
+
+ @Resource
+ ReactiveRedisTemplate reactiveRedisTemplate;
+
+ final static int testTimes = 10_000;
+ final static String hashKey = "hashKey";
+
+ @Test
+ @Order(1)
+ void kvTest() {
+ ValueOperations valueOperations = redisTemplate.opsForValue();
+
+ IntStream.range(0, testTimes).forEach(i -> {
+ String key = "field:" + i;
+ String value = "value:" + i;
+ valueOperations.set(key, value, Duration.ofMinutes(1));
+ });
+
+ Object o = valueOperations.get("field:1000");
+ System.out.println(Objects.requireNonNull(o).getClass() + " : " + o);
+ }
+
+ @Test
+ @Order(2)
+ void hashTest() {
+ HashOperations hashOperations = redisTemplate.opsForHash();
+
+ IntStream.range(0, testTimes).forEach(i -> {
+ String key = "field:" + i;
+ String value = "value:" + i;
+ hashOperations.put(hashKey, key, value);
+ });
+
+ redisTemplate.expire(hashKey, Duration.ofMinutes(1));
+
+ Map