mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-04 01:49:58 +08:00
* !44 comment * !39 添加下行日志打印 * !36 扩展计价领域模型 * !35 webui 初步成型 * !34 webui 初步成型
This commit is contained in:
48
.github/workflows/qodana_code_quality.yml
vendored
Normal file
48
.github/workflows/qodana_code_quality.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#
|
||||
# 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
# 微信:mohan_88888
|
||||
# 抖音:程序员三丙
|
||||
# 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
#
|
||||
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Discover additional configuration options in our documentation #
|
||||
# https://www.jetbrains.com/help/qodana/github.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
|
||||
name: Qodana
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- Feat_web_ui
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
- name: 'Qodana Scan'
|
||||
uses: JetBrains/qodana-action@v2025.2
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
with:
|
||||
args: --baseline,qodana.sarif.json
|
||||
# In pr-mode: 'true' Qodana checks only changed files
|
||||
pr-mode: false
|
||||
use-caches: true
|
||||
post-pr-comment: true
|
||||
use-annotations: true
|
||||
# Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job
|
||||
upload-result: false
|
||||
# quick-fixes available in Ultimate and Ultimate Plus plans
|
||||
push-fixes: 'none'
|
||||
@@ -9,7 +9,7 @@
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/sanbing/jcpp-base:1.0 AS base
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN mvn -U -B -T 0.8C clean install -DskipTests
|
||||
RUN mvn -U -B -T 4 clean package -DskipTests
|
||||
|
||||
#分层
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-bullseye AS builder
|
||||
@@ -37,7 +37,7 @@ RUN chmod a+x start.sh \
|
||||
&& chmod 700 -R /home/sanbing/logs/* \
|
||||
&& chown -R sanbing:sanbing /home/sanbing
|
||||
|
||||
EXPOSE 8080 8080
|
||||
EXPOSE 8080/tcp 38001/tcp 38002/tcp 38003/tcp 38011/tcp
|
||||
|
||||
ENV APP_LOG_LEVEL=INFO
|
||||
ENV PROTOCOLS_LOG_LEVEL=INFO
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#
|
||||
|
||||
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/sanbing/mvn:3.9.9-jdk21 AS base
|
||||
FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-bullseye AS base
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN mvn -U -B -T 0.8C clean install -DskipTests \
|
||||
RUN mvn -U -B -T 4 clean compile -DskipTests \
|
||||
&& rm -rf /app
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
|
||||
volumes:
|
||||
postgresql_data: {}
|
||||
jcpp_pg_data: {}
|
||||
|
||||
networks:
|
||||
sanbing-network:
|
||||
@@ -33,5 +33,4 @@ services:
|
||||
- 'POSTGRESQL_DEFAULT_TRANSACTION_ISOLATION=read committed'
|
||||
- 'TZ=Asia/Shanghai'
|
||||
volumes:
|
||||
- postgresql_data:/bitnami/postgresql
|
||||
- ./schema/schema-postgres.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- jcpp_pg_data:/bitnami/postgresql
|
||||
|
||||
@@ -37,7 +37,7 @@ RUN chmod a+x start.sh \
|
||||
&& chmod 700 -R /home/sanbing/logs/* \
|
||||
&& chown -R sanbing:sanbing /home/sanbing
|
||||
|
||||
EXPOSE 8080 8080
|
||||
EXPOSE 8080/tcp 38001/tcp 38002/tcp 38003/tcp 38011/tcp
|
||||
|
||||
ENV PROTOCOLS_LOG_LEVEL=INFO
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
--
|
||||
-- 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
-- 微信:mohan_88888
|
||||
-- 抖音:程序员三丙
|
||||
-- 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
--
|
||||
|
||||
|
||||
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 numeric(16, 8) default 0 not null,
|
||||
settlement_details jsonb,
|
||||
electricity_quantity numeric(16, 8) 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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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}"
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
微信:mohan_88888
|
||||
抖音:程序员三丙
|
||||
付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
|
||||
-->
|
||||
<configuration status="INFO" monitorInterval="30">
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,60 @@
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.passay</groupId>
|
||||
<artifactId>passay</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ua-parser</groupId>
|
||||
<artifactId>uap-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcutil-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-ext-jdk18on</artifactId>
|
||||
</dependency>
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- Web for REST API -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "spring.mvc.cors")
|
||||
public class MvcCorsProperties {
|
||||
|
||||
private Map<String, CorsConfiguration> mappings = new HashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import sanbing.jcpp.app.exception.JCPPErrorCode;
|
||||
import sanbing.jcpp.app.exception.JCPPErrorResponseHandler;
|
||||
import sanbing.jcpp.app.exception.JCPPException;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 基础控制器
|
||||
* 提供统一的异常处理机制,所有Controller都应该继承此类
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseController {
|
||||
|
||||
@Autowired
|
||||
private JCPPErrorResponseHandler errorResponseHandler;
|
||||
|
||||
/**
|
||||
* 处理所有通用异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public void handleControllerException(Exception e, HttpServletResponse response) {
|
||||
log.debug("Processing controller exception: {}", e.getMessage(), e);
|
||||
|
||||
JCPPException jcppException = handleException(e);
|
||||
|
||||
// 如果是通用错误且有具体的原因异常,则使用原始异常
|
||||
if (jcppException.getErrorCode() == JCPPErrorCode.GENERAL &&
|
||||
jcppException.getCause() instanceof Exception &&
|
||||
Objects.equals(jcppException.getCause().getMessage(), jcppException.getMessage())) {
|
||||
e = (Exception) jcppException.getCause();
|
||||
} else {
|
||||
e = jcppException;
|
||||
}
|
||||
|
||||
errorResponseHandler.handle(e, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理JCPPException异常
|
||||
* 直接委托给统一的错误处理器
|
||||
*/
|
||||
@ExceptionHandler(JCPPException.class)
|
||||
public void handleJCPPException(JCPPException ex, HttpServletResponse response) {
|
||||
log.debug("Processing JCPP exception: {}", ex.getMessage(), ex);
|
||||
errorResponseHandler.handle(ex, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
* 将Spring的验证异常转换为JCPPException
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) {
|
||||
log.warn("Validation error occurred: {}", validationError.getMessage());
|
||||
|
||||
// 提取字段错误信息
|
||||
String errorMessage = validationError.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
if (errorMessage.isEmpty()) {
|
||||
errorMessage = "Validation failed";
|
||||
} else {
|
||||
errorMessage = "Validation error: " + errorMessage;
|
||||
}
|
||||
|
||||
JCPPException jcppException = new JCPPException(errorMessage, JCPPErrorCode.BAD_REQUEST_PARAMS);
|
||||
handleControllerException(jcppException, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常转换处理方法
|
||||
* 将各种异常转换为JCPPException,统一异常处理流程
|
||||
*/
|
||||
private JCPPException handleException(Exception e) {
|
||||
if (e instanceof JCPPException jcppException) {
|
||||
return jcppException;
|
||||
}
|
||||
|
||||
// 处理运行时异常
|
||||
if (e instanceof RuntimeException) {
|
||||
if (e instanceof IllegalArgumentException) {
|
||||
return new JCPPException("Invalid argument: " + e.getMessage(), e, JCPPErrorCode.BAD_REQUEST_PARAMS);
|
||||
} else if (e instanceof IllegalStateException) {
|
||||
return new JCPPException("Invalid state: " + e.getMessage(), e, JCPPErrorCode.VERSION_CONFLICT);
|
||||
} else {
|
||||
return new JCPPException("Runtime error: " + e.getMessage(), e, JCPPErrorCode.GENERAL);
|
||||
}
|
||||
}
|
||||
|
||||
// 其他异常统一处理为通用错误
|
||||
return new JCPPException("Unexpected error: " + e.getMessage(), e, JCPPErrorCode.GENERAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.DashboardStats;
|
||||
import sanbing.jcpp.app.service.DashboardService;
|
||||
|
||||
/**
|
||||
* 仪表盘控制器
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/dashboard")
|
||||
@RequiredArgsConstructor
|
||||
public class DashboardController extends BaseController {
|
||||
|
||||
private final DashboardService dashboardService;
|
||||
|
||||
/**
|
||||
* 获取仪表盘统计数据
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
public ResponseEntity<ApiResponse<DashboardStats>> getStats() {
|
||||
DashboardStats stats = dashboardService.getDashboardStats();
|
||||
return ResponseEntity.ok(ApiResponse.success("获取仪表盘数据成功", stats));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import sanbing.jcpp.app.adapter.request.GunCreateRequest;
|
||||
import sanbing.jcpp.app.adapter.request.GunQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.request.GunUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.GunWithStatusResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.dal.entity.Gun;
|
||||
import sanbing.jcpp.app.service.GunService;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/guns")
|
||||
@RequiredArgsConstructor
|
||||
public class GunController extends BaseController {
|
||||
|
||||
private final GunService gunService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<Gun>> createGun(@Valid @RequestBody GunCreateRequest request) {
|
||||
Gun gun = gunService.createGun(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("创建成功", gun));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Gun>> getGun(@PathVariable UUID id) {
|
||||
Gun gun = gunService.findById(id);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", gun));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Gun>> updateGun(@PathVariable UUID id,
|
||||
@Valid @RequestBody GunUpdateRequest request) {
|
||||
Gun gun = gunService.updateGun(id, request);
|
||||
return ResponseEntity.ok(ApiResponse.success("更新成功", gun));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Void>> deleteGun(@PathVariable UUID id) {
|
||||
gunService.deleteGun(id);
|
||||
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<PageResponse<GunWithStatusResponse>>> queryGunsWithStatus(GunQueryRequest request) {
|
||||
PageResponse<GunWithStatusResponse> guns = gunService.queryGunsWithStatus(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", guns));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import sanbing.jcpp.app.adapter.request.PileCreateRequest;
|
||||
import sanbing.jcpp.app.adapter.request.PileQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.request.PileUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PileOptionResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PileWithStatusResponse;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.app.exception.JCPPException;
|
||||
import sanbing.jcpp.app.service.PileService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/piles")
|
||||
@RequiredArgsConstructor
|
||||
public class PileController extends BaseController {
|
||||
|
||||
private final PileService pileService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<Pile>> createPile(@Valid @RequestBody PileCreateRequest request) {
|
||||
Pile pile = pileService.createPile(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("创建成功", pile));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Pile>> getPile(@PathVariable UUID id) {
|
||||
Pile pile = pileService.findById(id);
|
||||
if (pile == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", pile));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Pile>> updatePile(@PathVariable UUID id,
|
||||
@Valid @RequestBody PileUpdateRequest request) throws JCPPException {
|
||||
Pile pile = pileService.updatePile(id, request);
|
||||
return ResponseEntity.ok(ApiResponse.success("更新成功", pile));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Void>> deletePile(@PathVariable UUID id) throws JCPPException {
|
||||
pileService.deletePile(id);
|
||||
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<PageResponse<PileWithStatusResponse>>> queryPilesWithStatus(PileQueryRequest request) {
|
||||
PageResponse<PileWithStatusResponse> piles = pileService.queryPilesWithStatus(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", piles));
|
||||
}
|
||||
|
||||
@GetMapping("/options")
|
||||
public ResponseEntity<ApiResponse<List<PileOptionResponse>>> getPileOptions() {
|
||||
List<PileOptionResponse> options = pileService.getPileOptions();
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", options));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.ProtocolOption;
|
||||
import sanbing.jcpp.app.service.ProtocolService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 协议管理控制器
|
||||
*
|
||||
* @author 九筒
|
||||
* @since 2024-12-22
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/protocols")
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolController extends BaseController {
|
||||
|
||||
private final ProtocolService protocolService;
|
||||
|
||||
/**
|
||||
* 获取所有支持的协议列表
|
||||
*/
|
||||
@GetMapping("/supported")
|
||||
public ResponseEntity<ApiResponse<List<ProtocolOption>>> getSupportedProtocols() {
|
||||
List<ProtocolOption> protocols = protocolService.getSupportedProtocols();
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", protocols));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import sanbing.jcpp.app.adapter.request.StationCreateRequest;
|
||||
import sanbing.jcpp.app.adapter.request.StationQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.request.StationUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.adapter.response.StationOption;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
import sanbing.jcpp.app.exception.JCPPException;
|
||||
import sanbing.jcpp.app.service.StationService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电站管理控制器
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/stations")
|
||||
@RequiredArgsConstructor
|
||||
public class StationController extends BaseController {
|
||||
|
||||
private final StationService stationService;
|
||||
|
||||
/**
|
||||
* 分页查询充电站
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<PageResponse<Station>>> getStations(StationQueryRequest request) {
|
||||
PageResponse<Station> result = stationService.getStations(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取充电站详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Station>> getStation(@PathVariable UUID id) {
|
||||
Station station = stationService.getStationById(id);
|
||||
if (station == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", station));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建充电站
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<Station>> createStation(@Valid @RequestBody StationCreateRequest request) {
|
||||
Station station = stationService.createStation(request);
|
||||
return ResponseEntity.ok(ApiResponse.success("创建成功", station));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新充电站
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Station>> updateStation(@PathVariable UUID id,
|
||||
@Valid @RequestBody StationUpdateRequest request) throws JCPPException {
|
||||
Station station = stationService.updateStation(id, request);
|
||||
return ResponseEntity.ok(ApiResponse.success("更新成功", station));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除充电站
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<ApiResponse<Void>> deleteStation(@PathVariable UUID id) throws JCPPException {
|
||||
stationService.deleteStation(id);
|
||||
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取充电站选项列表(用于下拉选择)
|
||||
*/
|
||||
@GetMapping("/options")
|
||||
public ResponseEntity<ApiResponse<List<StationOption>>> getStationOptions() {
|
||||
List<StationOption> options = stationService.getStationOptions();
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", options));
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索充电站选项列表(支持关键字搜索和分页)
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<ApiResponse<List<StationOption>>> searchStationOptions(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
List<StationOption> options = stationService.searchStationOptions(keyword, page, size);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", options));
|
||||
}
|
||||
}
|
||||
@@ -4,46 +4,48 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter;
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import sanbing.jcpp.app.service.PileProtocolService;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto;
|
||||
import sanbing.jcpp.proto.gen.ProtocolProto.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
@RestController
|
||||
public class TestController {
|
||||
@RequestMapping("/test")
|
||||
public class TestController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private PileProtocolService pileProtocolService;
|
||||
|
||||
@GetMapping("/api/startCharge")
|
||||
@GetMapping("/startCharge")
|
||||
public ResponseEntity<String> startCharge() {
|
||||
|
||||
String orderNo = "ORD" + RandomStringUtils.secure().nextNumeric(20);
|
||||
String logicalCardNo = RandomStringUtils.secure().nextNumeric(12);
|
||||
String physicalCardNo = RandomStringUtils.secure().nextNumeric(12);
|
||||
|
||||
pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("50"), orderNo,
|
||||
pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("50"), orderNo,
|
||||
logicalCardNo, physicalCardNo, null);
|
||||
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/parallelStartCharge")
|
||||
@GetMapping("/parallelStartCharge")
|
||||
public ResponseEntity<String> parallelStartCharge() {
|
||||
|
||||
String orderNo = "PAR" + RandomStringUtils.secure().nextNumeric(20);
|
||||
@@ -51,13 +53,13 @@ public class TestController {
|
||||
String physicalCardNo = RandomStringUtils.secure().nextNumeric(12);
|
||||
String parallelNo = RandomStringUtils.secure().nextNumeric(6);
|
||||
|
||||
pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("100"),
|
||||
pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("100"),
|
||||
orderNo, logicalCardNo, physicalCardNo, parallelNo);
|
||||
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/stopCharge")
|
||||
@GetMapping("/stopCharge")
|
||||
public ResponseEntity<String> stopCharge() {
|
||||
|
||||
pileProtocolService.stopCharge("20231212000010", "01");
|
||||
@@ -65,7 +67,7 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/restartPile")
|
||||
@GetMapping("/restartPile")
|
||||
public ResponseEntity<String> restartPile() {
|
||||
|
||||
pileProtocolService.restartPile("20231212000010", 1);
|
||||
@@ -73,7 +75,7 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/setPricing")
|
||||
@GetMapping("/setPricing")
|
||||
public ResponseEntity<String> setPricing() {
|
||||
|
||||
String pileCode = "20231212000010";
|
||||
@@ -159,12 +161,8 @@ public class TestController {
|
||||
flagPriceMap.put(PricingModelFlag.FLAT_VALUE, flagPriceFlat);
|
||||
flagPriceMap.put(PricingModelFlag.VALLEY_VALUE, flagPriceValley);
|
||||
|
||||
// 构建 PricingModelProto 对象
|
||||
PricingModelProto pricingModel = PricingModelProto.newBuilder()
|
||||
.setType(PricingModelType.CHARGE) // 设置为充电计费模型
|
||||
.setRule(PricingModelRule.SPLIT_TIME) // 使用分时计费规则
|
||||
.setStandardElec("1.0") // 标准电费(默认值)
|
||||
.setStandardServ("0.3") // 标准服务费(默认值)
|
||||
// 构建峰谷计价配置
|
||||
PeakValleyPricingProto peakValleyPricing = PeakValleyPricingProto.newBuilder()
|
||||
.putAllFlagPrice(flagPriceMap) // 设置尖峰平谷对应的价格
|
||||
.addPeriod(topPeriod1) // 添加尖峰时段1
|
||||
.addPeriod(topPeriod2) // 添加尖峰时段2
|
||||
@@ -174,6 +172,13 @@ public class TestController {
|
||||
.addPeriod(flatPeriod2) // 添加平时段2
|
||||
.addPeriod(valleyPeriod) // 添加谷时段
|
||||
.build();
|
||||
|
||||
// 构建 PricingModelProto 对象
|
||||
PricingModelProto pricingModel = PricingModelProto.newBuilder()
|
||||
.setType(PricingModelType.CHARGE) // 设置为充电计费模型
|
||||
.setRule(PricingModelRule.PEAK_VALLEY_PRICING) // 使用峰谷计费规则
|
||||
.setPeakValleyPricing(peakValleyPricing) // 设置峰谷计价配置
|
||||
.build();
|
||||
|
||||
pileProtocolService.setPricing(pileCode,
|
||||
SetPricingRequest.newBuilder()
|
||||
@@ -185,11 +190,89 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/timePeriodPricing")
|
||||
public ResponseEntity<String> testTimePeriodPricing() {
|
||||
String pileCode = "TEST001";
|
||||
|
||||
@GetMapping("/api/otaRequest")
|
||||
// 创建时段计价列表
|
||||
List<TimePeriodItemProto> timePeriodItems = new ArrayList<>();
|
||||
|
||||
// 深夜时段 (00:00-06:00)
|
||||
timePeriodItems.add(TimePeriodItemProto.newBuilder()
|
||||
.setPeriodNo(1)
|
||||
.setStartTime("00:00:00")
|
||||
.setEndTime("06:00:00")
|
||||
.setElecPrice("0.40")
|
||||
.setServPrice("0.20")
|
||||
.setDescription("深夜时段")
|
||||
.build());
|
||||
|
||||
// 早高峰时段 (06:00-10:00)
|
||||
timePeriodItems.add(TimePeriodItemProto.newBuilder()
|
||||
.setPeriodNo(2)
|
||||
.setStartTime("06:00:00")
|
||||
.setEndTime("10:00:00")
|
||||
.setElecPrice("0.80")
|
||||
.setServPrice("0.50")
|
||||
.setDescription("早高峰时段")
|
||||
.build());
|
||||
|
||||
// 日间平时段 (10:00-18:00)
|
||||
timePeriodItems.add(TimePeriodItemProto.newBuilder()
|
||||
.setPeriodNo(3)
|
||||
.setStartTime("10:00:00")
|
||||
.setEndTime("18:00:00")
|
||||
.setElecPrice("0.65")
|
||||
.setServPrice("0.35")
|
||||
.setDescription("日间平时段")
|
||||
.build());
|
||||
|
||||
// 晚高峰时段 (18:00-22:00)
|
||||
timePeriodItems.add(TimePeriodItemProto.newBuilder()
|
||||
.setPeriodNo(4)
|
||||
.setStartTime("18:00:00")
|
||||
.setEndTime("22:00:00")
|
||||
.setElecPrice("0.90")
|
||||
.setServPrice("0.60")
|
||||
.setDescription("晚高峰时段")
|
||||
.build());
|
||||
|
||||
// 夜间时段 (22:00-24:00)
|
||||
timePeriodItems.add(TimePeriodItemProto.newBuilder()
|
||||
.setPeriodNo(5)
|
||||
.setStartTime("22:00:00")
|
||||
.setEndTime("23:59:59")
|
||||
.setElecPrice("0.50")
|
||||
.setServPrice("0.25")
|
||||
.setDescription("夜间时段")
|
||||
.build());
|
||||
|
||||
// 构建时段计价配置
|
||||
TimePeriodPricingProto timePeriodPricing = TimePeriodPricingProto.newBuilder()
|
||||
.addAllPeriods(timePeriodItems)
|
||||
.build();
|
||||
|
||||
// 构建 PricingModelProto 对象
|
||||
PricingModelProto pricingModel = PricingModelProto.newBuilder()
|
||||
.setType(PricingModelType.CHARGE) // 设置为充电计费模型
|
||||
.setRule(PricingModelRule.TIME_PERIOD_PRICING) // 使用时段计价规则
|
||||
.setTimePeriodPricing(timePeriodPricing) // 设置时段计价配置
|
||||
.build();
|
||||
|
||||
pileProtocolService.setPricing(pileCode,
|
||||
SetPricingRequest.newBuilder()
|
||||
.setPileCode(pileCode)
|
||||
.setPricingId(2000L)
|
||||
.setPricingModel(pricingModel)
|
||||
.build());
|
||||
|
||||
return ResponseEntity.ok("Time period pricing test success");
|
||||
}
|
||||
|
||||
@GetMapping("/otaRequest")
|
||||
public ResponseEntity<String> otaRequest() {
|
||||
|
||||
pileProtocolService.otaRequest(ProtocolProto.OtaRequest.newBuilder()
|
||||
pileProtocolService.otaRequest(OtaRequest.newBuilder()
|
||||
.setAddress("127.0.0.1")
|
||||
.setExecutionControl(1)
|
||||
.setDownloadTimeout(1)
|
||||
@@ -205,7 +288,7 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/offlineCardBalanceUpdateRequest")
|
||||
@GetMapping("/offlineCardBalanceUpdateRequest")
|
||||
public ResponseEntity<String> offlineCardBalanceUpdateRequest() {
|
||||
|
||||
pileProtocolService.offlineCardBalanceUpdateRequest(OfflineCardBalanceUpdateRequest.newBuilder()
|
||||
@@ -218,7 +301,7 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/offlineCardSyncRequest")
|
||||
@GetMapping("/offlineCardSyncRequest")
|
||||
public ResponseEntity<String> offlineCardSyncRequest() {
|
||||
|
||||
List<CardInfo> cardInfos = Lists.newArrayList(CardInfo.newBuilder().setCardNo("1000000000123456").setLogicCardNo("1000000000123456").build(),
|
||||
@@ -234,7 +317,7 @@ public class TestController {
|
||||
return ResponseEntity.ok("success");
|
||||
}
|
||||
|
||||
@GetMapping("/api/timeSync")
|
||||
@GetMapping("/timeSync")
|
||||
public ResponseEntity<String> timeSync() {
|
||||
pileProtocolService.timeSync("20231212000010", LocalDateTime.now());
|
||||
return ResponseEntity.ok("success");
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.ErrorCode;
|
||||
import sanbing.jcpp.app.adapter.response.LoginResponse;
|
||||
import sanbing.jcpp.app.service.security.model.SecurityUser;
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController extends BaseController {
|
||||
|
||||
@GetMapping("/info")
|
||||
public ResponseEntity<ApiResponse<LoginResponse.UserInfo>> getUserInfo() {
|
||||
try {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !(authentication.getPrincipal() instanceof SecurityUser securityUser)) {
|
||||
return ResponseEntity.status(401).body(ApiResponse.error(ErrorCode.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
LoginResponse.UserInfo userInfo = LoginResponse.UserInfo.builder()
|
||||
.id(securityUser.getId().toString())
|
||||
.username(securityUser.getUserName())
|
||||
.status(securityUser.isEnabled() ? "ENABLE" : "DISABLE")
|
||||
.build();
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(userInfo));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息异常", e);
|
||||
return ResponseEntity.status(500).body(ApiResponse.error("获取用户信息失败"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* SPA(单页应用)路由控制器
|
||||
* 处理所有前端路由,返回index.html
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Controller
|
||||
public class WebController extends BaseController {
|
||||
|
||||
/**
|
||||
* 处理所有业务页面路由
|
||||
* 统一使用 /page/ 前缀,便于扩展管理
|
||||
*/
|
||||
@GetMapping(value = {"/", "/login", "/page/**"})
|
||||
public String redirect() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class GunCreateRequest {
|
||||
|
||||
@NotBlank(message = "充电枪名称不能为空")
|
||||
@NoXss
|
||||
private String gunName;
|
||||
|
||||
@NotBlank(message = "充电枪编号不能为空")
|
||||
@NoXss
|
||||
private String gunNo;
|
||||
|
||||
@NotBlank(message = "充电枪编码不能为空")
|
||||
@NoXss
|
||||
private String gunCode;
|
||||
|
||||
@NotNull(message = "充电站ID不能为空")
|
||||
private UUID stationId;
|
||||
|
||||
@NotNull(message = "充电桩ID不能为空")
|
||||
private UUID pileId;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GunQueryRequest extends PageRequest {
|
||||
|
||||
private String gunName;
|
||||
|
||||
private String gunNo;
|
||||
|
||||
private String gunCode;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private UUID pileId;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
@Data
|
||||
public class GunUpdateRequest {
|
||||
|
||||
@NotBlank(message = "充电枪名称不能为空")
|
||||
@NoXss
|
||||
private String gunName;
|
||||
|
||||
private GunRunStatusEnum runStatus;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页查询请求基类
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class PageRequest {
|
||||
|
||||
private Integer page = 1; // 页码,从1开始
|
||||
private Integer size = 10; // 每页大小
|
||||
private String sortField; // 排序字段
|
||||
private String sortOrder = "desc"; // 排序方向:asc, desc
|
||||
private String search; // 搜索关键词
|
||||
|
||||
/**
|
||||
* 获取MyBatis-Plus的页码(从0开始)
|
||||
*/
|
||||
public long getOffset() {
|
||||
return (long) (page - 1) * size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容方法:获取排序字段
|
||||
*/
|
||||
public String getSortBy() {
|
||||
return sortField;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class PileCreateRequest {
|
||||
|
||||
@NotBlank(message = "充电桩名称不能为空")
|
||||
@NoXss
|
||||
private String pileName;
|
||||
|
||||
@NotBlank(message = "充电桩编码不能为空")
|
||||
@NoXss
|
||||
private String pileCode;
|
||||
|
||||
@NotBlank(message = "协议不能为空")
|
||||
@NoXss
|
||||
private String protocol;
|
||||
|
||||
@NotNull(message = "充电站ID不能为空")
|
||||
private UUID stationId;
|
||||
|
||||
@NoXss
|
||||
private String brand;
|
||||
|
||||
@NoXss
|
||||
private String model;
|
||||
|
||||
@NoXss
|
||||
private String manufacturer;
|
||||
|
||||
private PileTypeEnum type = PileTypeEnum.DC;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PileQueryRequest extends PageRequest {
|
||||
|
||||
private String pileName;
|
||||
|
||||
private String pileCode;
|
||||
|
||||
private String protocol;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private String brand;
|
||||
|
||||
private String model;
|
||||
|
||||
private String manufacturer;
|
||||
|
||||
private PileTypeEnum type;
|
||||
|
||||
private PileStatusEnum status;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
@Data
|
||||
public class PileUpdateRequest {
|
||||
|
||||
@NotBlank(message = "充电桩名称不能为空")
|
||||
@NoXss
|
||||
private String pileName;
|
||||
|
||||
@NotBlank(message = "协议不能为空")
|
||||
@NoXss
|
||||
private String protocol;
|
||||
|
||||
@NoXss
|
||||
private String brand;
|
||||
|
||||
@NoXss
|
||||
private String model;
|
||||
|
||||
@NoXss
|
||||
private String manufacturer;
|
||||
|
||||
private PileTypeEnum type;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
|
||||
/**
|
||||
* 创建充电站请求
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class StationCreateRequest {
|
||||
|
||||
@NotBlank(message = "充电站名称不能为空")
|
||||
@NoXss
|
||||
private String stationName;
|
||||
|
||||
@NotBlank(message = "充电站编码不能为空")
|
||||
@NoXss
|
||||
private String stationCode;
|
||||
|
||||
private Float longitude; // 经度
|
||||
private Float latitude; // 纬度
|
||||
|
||||
@NoXss
|
||||
private String province; // 省份
|
||||
|
||||
@NoXss
|
||||
private String city; // 城市
|
||||
|
||||
@NoXss
|
||||
private String county; // 区县
|
||||
|
||||
@NoXss
|
||||
private String address; // 详细地址
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
|
||||
/**
|
||||
* 充电站查询请求
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class StationQueryRequest extends PageRequest {
|
||||
|
||||
private String stationName; // 充电站名称
|
||||
private String stationCode; // 充电站编码
|
||||
private String province; // 省份
|
||||
private String city; // 城市
|
||||
private String county; // 区县
|
||||
private String address; // 详细地址
|
||||
private String keyword; // 关键字搜索(站名或编码)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
|
||||
/**
|
||||
* 更新充电站请求
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class StationUpdateRequest {
|
||||
|
||||
@NotBlank(message = "充电站名称不能为空")
|
||||
@NoXss
|
||||
private String stationName;
|
||||
|
||||
private Float longitude; // 经度
|
||||
private Float latitude; // 纬度
|
||||
|
||||
@NoXss
|
||||
private String province; // 省份
|
||||
|
||||
@NoXss
|
||||
private String city; // 城市
|
||||
|
||||
@NoXss
|
||||
private String county; // 区县
|
||||
|
||||
@NoXss
|
||||
private String address; // 详细地址
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 通用API响应结果
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private String errorCode;
|
||||
private String message;
|
||||
private T data;
|
||||
private long timestamp;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.message("操作成功")
|
||||
.data(data)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.message(message)
|
||||
.data(data)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String errorCode, String message) {
|
||||
return ApiResponse.<T>builder()
|
||||
.errorCode(errorCode)
|
||||
.message(message)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(ErrorCode errorCode, String message) {
|
||||
return ApiResponse.<T>builder()
|
||||
.errorCode(errorCode.getCode())
|
||||
.message(message)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(ErrorCode errorCode) {
|
||||
return ApiResponse.<T>builder()
|
||||
.errorCode(errorCode.getCode())
|
||||
.message(errorCode.getMessage())
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return error(ErrorCode.BUSINESS_ERROR, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 仪表盘统计数据
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DashboardStats {
|
||||
|
||||
/**
|
||||
* 总览统计
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Overview {
|
||||
private Long totalStations; // 总充电站数
|
||||
private Long totalPiles; // 总充电桩数
|
||||
private Long totalGuns; // 总充电枪数
|
||||
}
|
||||
|
||||
/**
|
||||
* 充电桩在线状态分布
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PileStatusDistribution {
|
||||
private Long onlinePiles; // 在线充电桩数
|
||||
private Long offlinePiles; // 离线充电桩数
|
||||
private Long totalPiles; // 总充电桩数
|
||||
|
||||
public double getOnlinePercentage() {
|
||||
return totalPiles > 0 ? (onlinePiles * 100.0) / totalPiles : 0.0;
|
||||
}
|
||||
|
||||
public double getOfflinePercentage() {
|
||||
return totalPiles > 0 ? (offlinePiles * 100.0) / totalPiles : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 充电枪运行状态分布
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class GunStatusDistribution {
|
||||
private Long idleGuns; // 空闲 (IDLE)
|
||||
private Long insertedGuns; // 已插枪未充电 (INSERTED)
|
||||
private Long chargingGuns; // 充电中 (CHARGING)
|
||||
private Long chargeCompleteGuns; // 充电完成 (CHARGE_COMPLETE)
|
||||
private Long dischargeReadyGuns; // 放电准备 (DISCHARGE_READY)
|
||||
private Long dischargingGuns; // 放电中 (DISCHARGING)
|
||||
private Long dischargeCompleteGuns; // 放电完成 (DISCHARGE_COMPLETE)
|
||||
private Long reservedGuns; // 预约 (RESERVED)
|
||||
private Long faultGuns; // 故障 (FAULT)
|
||||
private Long totalGuns; // 总充电枪数
|
||||
|
||||
public double getIdlePercentage() {
|
||||
return totalGuns > 0 ? (idleGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getInsertedPercentage() {
|
||||
return totalGuns > 0 ? (insertedGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getChargingPercentage() {
|
||||
return totalGuns > 0 ? (chargingGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getChargeCompletePercentage() {
|
||||
return totalGuns > 0 ? (chargeCompleteGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getDischargeReadyPercentage() {
|
||||
return totalGuns > 0 ? (dischargeReadyGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getDischargingPercentage() {
|
||||
return totalGuns > 0 ? (dischargingGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getDischargeCompletePercentage() {
|
||||
return totalGuns > 0 ? (dischargeCompleteGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getReservedPercentage() {
|
||||
return totalGuns > 0 ? (reservedGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
|
||||
public double getFaultPercentage() {
|
||||
return totalGuns > 0 ? (faultGuns * 100.0) / totalGuns : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
private Overview overview;
|
||||
private PileStatusDistribution pileStatusDistribution;
|
||||
private GunStatusDistribution gunStatusDistribution;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
/**
|
||||
* 统一错误码管理
|
||||
* 避免魔法值硬编码,便于维护和扩展
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
// ==================== 通用错误码 ====================
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS("SUCCESS", "操作成功"),
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
SYSTEM_ERROR("SYSTEM_ERROR", "系统异常,请稍后重试"),
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
BUSINESS_ERROR("BUSINESS_ERROR", "业务处理失败"),
|
||||
|
||||
// ==================== 参数校验相关 ====================
|
||||
/**
|
||||
* 参数校验失败
|
||||
*/
|
||||
VALIDATION_ERROR("VALIDATION_ERROR", "参数校验失败"),
|
||||
|
||||
/**
|
||||
* 数据绑定异常
|
||||
*/
|
||||
BINDING_ERROR("BINDING_ERROR", "数据绑定异常"),
|
||||
|
||||
/**
|
||||
* 非法参数
|
||||
*/
|
||||
ILLEGAL_ARGUMENT("ILLEGAL_ARGUMENT", "参数错误"),
|
||||
|
||||
/**
|
||||
* 非法状态
|
||||
*/
|
||||
ILLEGAL_STATE("ILLEGAL_STATE", "状态错误"),
|
||||
|
||||
// ==================== 认证授权相关 ====================
|
||||
/**
|
||||
* 未认证
|
||||
*/
|
||||
UNAUTHORIZED("UNAUTHORIZED", "用户未认证"),
|
||||
|
||||
/**
|
||||
* 认证失败
|
||||
*/
|
||||
AUTH_FAILED("AUTH_FAILED", "用户名或密码错误"),
|
||||
|
||||
/**
|
||||
* JWT认证失败
|
||||
*/
|
||||
JWT_AUTH_FAILED("JWT_AUTH_FAILED", "JWT Token认证失败"),
|
||||
|
||||
/**
|
||||
* 权限不足
|
||||
*/
|
||||
FORBIDDEN("FORBIDDEN", "权限不足"),
|
||||
|
||||
// ==================== 资源相关 ====================
|
||||
/**
|
||||
* 资源不存在
|
||||
*/
|
||||
NOT_FOUND("NOT_FOUND", "请求的资源不存在"),
|
||||
|
||||
/**
|
||||
* 资源冲突
|
||||
*/
|
||||
CONFLICT("CONFLICT", "资源冲突"),
|
||||
|
||||
// ==================== 业务特定错误码 ====================
|
||||
/**
|
||||
* 充电桩编码已存在
|
||||
*/
|
||||
PILE_CODE_EXISTS("PILE_CODE_EXISTS", "充电桩编码已存在"),
|
||||
|
||||
/**
|
||||
* 充电站名称已存在
|
||||
*/
|
||||
STATION_NAME_EXISTS("STATION_NAME_EXISTS", "充电站名称已存在"),
|
||||
|
||||
/**
|
||||
* 充电枪编号已存在
|
||||
*/
|
||||
GUN_CODE_EXISTS("GUN_CODE_EXISTS", "充电枪编号已存在"),
|
||||
|
||||
/**
|
||||
* 充电桩不存在
|
||||
*/
|
||||
PILE_NOT_FOUND("PILE_NOT_FOUND", "充电桩不存在"),
|
||||
|
||||
/**
|
||||
* 充电站不存在
|
||||
*/
|
||||
STATION_NOT_FOUND("STATION_NOT_FOUND", "充电站不存在"),
|
||||
|
||||
/**
|
||||
* 充电枪不存在
|
||||
*/
|
||||
GUN_NOT_FOUND("GUN_NOT_FOUND", "充电枪不存在");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
ErrorCode(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据错误码查找枚举
|
||||
*
|
||||
* @param code 错误码
|
||||
* @return 对应的枚举,如果不存在返回null
|
||||
*/
|
||||
public static ErrorCode fromCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ErrorCode errorCode : ErrorCode.values()) {
|
||||
if (errorCode.getCode().equals(code)) {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电枪响应DTO(包含状态信息)
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class GunWithStatusResponse {
|
||||
|
||||
/**
|
||||
* 充电枪ID
|
||||
*/
|
||||
private UUID id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 充电枪名称
|
||||
*/
|
||||
private String gunName;
|
||||
|
||||
/**
|
||||
* 充电枪编号,不允许修改
|
||||
*/
|
||||
private String gunNo;
|
||||
|
||||
/**
|
||||
* 充电枪编码,不允许修改
|
||||
*/
|
||||
private String gunCode;
|
||||
|
||||
/**
|
||||
* 充电站ID
|
||||
*/
|
||||
private UUID stationId;
|
||||
|
||||
/**
|
||||
* 充电桩ID
|
||||
*/
|
||||
private UUID pileId;
|
||||
|
||||
/**
|
||||
* 充电站名称
|
||||
*/
|
||||
private String stationName;
|
||||
|
||||
/**
|
||||
* 充电桩名称
|
||||
*/
|
||||
private String pileName;
|
||||
|
||||
/**
|
||||
* 充电桩编码
|
||||
*/
|
||||
private String pileCode;
|
||||
|
||||
/**
|
||||
* 附加信息
|
||||
*/
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private Integer version;
|
||||
|
||||
// ========== 状态信息 ==========
|
||||
|
||||
/**
|
||||
* 充电枪运行状态
|
||||
*/
|
||||
private GunRunStatusEnum runStatus;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 登录响应DTO
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginResponse {
|
||||
|
||||
private String token;
|
||||
private String refreshToken;
|
||||
@Builder.Default
|
||||
private String tokenType = "Bearer";
|
||||
private UserInfo user;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class UserInfo {
|
||||
private String id;
|
||||
private String username;
|
||||
private String status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页响应
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PageResponse<T> {
|
||||
|
||||
private List<T> records; // 数据列表
|
||||
private Long total; // 总记录数
|
||||
private Integer page; // 当前页码
|
||||
private Integer size; // 每页大小
|
||||
private Integer totalPages; // 总页数
|
||||
|
||||
public static <T> PageResponse<T> of(List<T> records, Long total, Integer page, Integer size) {
|
||||
int totalPages = (int) Math.ceil((double) total / size);
|
||||
return PageResponse.<T>builder()
|
||||
.records(records)
|
||||
.total(total)
|
||||
.page(page)
|
||||
.size(size)
|
||||
.totalPages(totalPages)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PileOptionResponse {
|
||||
|
||||
private UUID id;
|
||||
|
||||
private String label; // 显示名称,格式:pileName (pileCode)
|
||||
|
||||
private String pileName;
|
||||
|
||||
private String pileCode;
|
||||
|
||||
private UUID stationId;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电桩响应DTO(包含状态信息)
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class PileWithStatusResponse {
|
||||
|
||||
/**
|
||||
* 充电桩ID
|
||||
*/
|
||||
private UUID id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 充电桩名称
|
||||
*/
|
||||
private String pileName;
|
||||
|
||||
/**
|
||||
* 充电桩编码,不允许修改
|
||||
*/
|
||||
private String pileCode;
|
||||
|
||||
/**
|
||||
* 协议类型
|
||||
*/
|
||||
private String protocol;
|
||||
|
||||
/**
|
||||
* 充电站ID
|
||||
*/
|
||||
private UUID stationId;
|
||||
|
||||
/**
|
||||
* 品牌
|
||||
*/
|
||||
private String brand;
|
||||
|
||||
/**
|
||||
* 型号
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 制造商
|
||||
*/
|
||||
private String manufacturer;
|
||||
|
||||
/**
|
||||
* 充电桩类型(交流/直流)
|
||||
*/
|
||||
private PileTypeEnum type;
|
||||
|
||||
/**
|
||||
* 附加信息
|
||||
*/
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private Integer version;
|
||||
|
||||
// ========== 状态信息 ==========
|
||||
|
||||
/**
|
||||
* 充电桩状态
|
||||
*/
|
||||
private PileStatusEnum status;
|
||||
|
||||
/**
|
||||
* 最近连接时间(13位时间戳)
|
||||
*/
|
||||
private Long connectedAt;
|
||||
|
||||
/**
|
||||
* 最后断线时间(13位时间戳)
|
||||
*/
|
||||
private Long disconnectedAt;
|
||||
|
||||
/**
|
||||
* 最后活跃时间(13位时间戳)
|
||||
*/
|
||||
private Long lastActiveTime;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.protocol.enums.SupportedProtocols;
|
||||
|
||||
/**
|
||||
* 协议选项响应
|
||||
* 用于前端下拉选择组件
|
||||
*
|
||||
* @author 九筒
|
||||
* @since 2024-12-22
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ProtocolOption {
|
||||
|
||||
private String value; // 协议标识符(用于表单提交)
|
||||
private String label; // 显示名称(用于前端显示)
|
||||
|
||||
/**
|
||||
* 从协议信息创建选项
|
||||
* @param protocolInfo 协议信息
|
||||
* @return 协议选项
|
||||
*/
|
||||
public static ProtocolOption fromProtocolInfo(SupportedProtocols.ProtocolInfo protocolInfo) {
|
||||
return new ProtocolOption(
|
||||
protocolInfo.protocolId(),
|
||||
protocolInfo.displayName()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电站选项响应
|
||||
* 用于下拉选择组件
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class StationOption {
|
||||
|
||||
private UUID id; // 充电站ID
|
||||
private String label; // 显示名称:stationName (stationCode)
|
||||
private String stationName; // 充电站名称
|
||||
private String stationCode; // 充电站编码
|
||||
|
||||
public static StationOption of(UUID id, String stationName, String stationCode) {
|
||||
StationOption option = new StationOption();
|
||||
option.setId(id);
|
||||
option.setStationName(stationName);
|
||||
option.setStationCode(stationCode);
|
||||
option.setLabel(stationName + " (" + stationCode + ")");
|
||||
return option;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* 用户权限枚举
|
||||
* 对应 sanbing.jcpp.app.service.security.model.Authority
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum AuthorityEnum implements IEnum<String> {
|
||||
|
||||
/**
|
||||
* 系统管理员
|
||||
*/
|
||||
SYS_ADMIN,
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
REFRESH_TOKEN,
|
||||
;
|
||||
|
||||
|
||||
public static AuthorityEnum parse(String value) {
|
||||
AuthorityEnum authority = null;
|
||||
if (value != null && !value.isEmpty()) {
|
||||
for (AuthorityEnum current : AuthorityEnum.values()) {
|
||||
if (current.name().equalsIgnoreCase(value)) {
|
||||
authority = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return this.name();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,17 @@ package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum GunRunStatusEnum implements IEnum<String> {
|
||||
IDLE, // 空闲
|
||||
INSERTED, // 已插枪
|
||||
CHARGING, // 充电中
|
||||
CHARGE_COMPLETE, // 充电完成
|
||||
INSERTED, // 已插枪 占用(未充电)
|
||||
CHARGING, // 充电中 占用(充电中)
|
||||
CHARGE_COMPLETE, // 充电完成 占用(预约锁定)
|
||||
DISCHARGE_READY, // 放电准备
|
||||
DISCHARGING, // 放电中
|
||||
DISCHARGE_COMPLETE, // 放电完成
|
||||
RESERVED, // 预约
|
||||
RESERVED, // 预约 占用(预约锁定)
|
||||
FAULT; // 故障
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,39 @@ package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* 充电桩状态枚举 - 简化版本,只维护在线/离线状态
|
||||
* <p>
|
||||
* 设计原则:
|
||||
* - 充电桩状态独立于充电枪状态,不受枪的工作状态影响
|
||||
* - 只关注设备的网络连接状态和基础可用性
|
||||
* - 充电枪的具体工作状态通过GunRunStatusEnum单独维护
|
||||
* <p>
|
||||
* 状态转换场景:
|
||||
* 1. 设备登录成功 → ONLINE
|
||||
* 2. 设备心跳正常 → 保持ONLINE
|
||||
* 3. 设备断开连接 → OFFLINE
|
||||
* 4. 设备超时无响应 → OFFLINE
|
||||
* 5. 系统重启后清洗 → 根据连接状态决定ONLINE/OFFLINE
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum PileStatusEnum implements IEnum<String> {
|
||||
IDLE, // 空闲
|
||||
WORKING, // 工作中
|
||||
FAULT, // 故障
|
||||
MAINTENANCE, // 维护中
|
||||
OFFLINE, // 离线
|
||||
/**
|
||||
* 在线状态:设备已连接并能正常通信
|
||||
* - 设备登录成功
|
||||
* - 心跳正常
|
||||
* - 能接收和响应指令
|
||||
*/
|
||||
ONLINE,
|
||||
|
||||
/**
|
||||
* 离线状态:设备未连接或无法通信
|
||||
* - 设备未登录
|
||||
* - 网络连接断开
|
||||
* - 心跳超时
|
||||
* - 系统重启后未重新连接
|
||||
*/
|
||||
OFFLINE,
|
||||
;
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,7 +9,7 @@ package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum PileTypeEnum implements IEnum<String> {
|
||||
AC, // 交流充电桩
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ package sanbing.jcpp.app.dal.config.ibatis.enums;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum UserStatusEnum implements IEnum<String> {
|
||||
ENABLE,
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.config.ibatis.typehandlers;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.postgresql.util.PGobject;
|
||||
import sanbing.jcpp.app.service.security.model.UserCredentials;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* UserCredentials 类型处理器
|
||||
* 负责 PostgreSQL JSONB 和 UserCredentials 对象之间的转换
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Slf4j
|
||||
@MappedTypes({UserCredentials.class})
|
||||
public class UserCredentialsTypeHandler extends BaseTypeHandler<UserCredentials> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, UserCredentials parameter, JdbcType jdbcType) throws SQLException {
|
||||
if (ps == null) {
|
||||
throw new SQLException("PreparedStatement cannot be null");
|
||||
}
|
||||
|
||||
if (parameter != null) {
|
||||
try {
|
||||
PGobject jsonObject = new PGobject();
|
||||
jsonObject.setType("jsonb");
|
||||
jsonObject.setValue(JacksonUtil.toString(parameter));
|
||||
ps.setObject(i, jsonObject);
|
||||
log.debug("Set UserCredentials parameter at index {}: failedLoginAttempts={}", i, parameter.getFailedLoginAttempts());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize UserCredentials to JSONB", e);
|
||||
throw new SQLException("Failed to serialize UserCredentials", e);
|
||||
}
|
||||
} else {
|
||||
ps.setNull(i, java.sql.Types.OTHER);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserCredentials getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return parseUserCredentials(rs.getString(columnName), columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserCredentials getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return parseUserCredentials(rs.getString(columnIndex), "column_" + columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserCredentials getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return parseUserCredentials(cs.getString(columnIndex), "column_" + columnIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 字符串为 UserCredentials 对象
|
||||
*/
|
||||
private UserCredentials parseUserCredentials(String jsonString, String columnIdentifier) {
|
||||
if (jsonString == null || jsonString.trim().isEmpty()) {
|
||||
log.debug("UserCredentials JSON is null or empty for {}", columnIdentifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
UserCredentials userCredentials = JacksonUtil.fromString(jsonString, UserCredentials.class);
|
||||
if (userCredentials != null) {
|
||||
log.debug("Parsed UserCredentials from {}: failedLoginAttempts={}",
|
||||
columnIdentifier, userCredentials.getFailedLoginAttempts());
|
||||
}
|
||||
return userCredentials;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse UserCredentials from JSON: {} for {}", jsonString, columnIdentifier, e);
|
||||
// 返回 null 而不是抛出异常,避免查询失败
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import sanbing.jcpp.app.data.kv.*;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 属性实体,用于存储设备的最新属性数据
|
||||
* 采用键值对存储结构设计
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_attr")
|
||||
public class Attribute implements Serializable, HasVersion {
|
||||
|
||||
/**
|
||||
* 实体ID (UUID保证全局唯一)
|
||||
* 复合主键的一部分
|
||||
*/
|
||||
@TableId(value = "entity_id", type = IdType.INPUT)
|
||||
private UUID entityId;
|
||||
|
||||
/**
|
||||
* 属性键 (字符串类型提高可读性)
|
||||
* 复合主键的一部分
|
||||
*/
|
||||
@TableField("attr_key")
|
||||
private String attrKey;
|
||||
|
||||
/**
|
||||
* 布尔值
|
||||
*/
|
||||
@TableField("bool_v")
|
||||
private Boolean boolV;
|
||||
|
||||
/**
|
||||
* 字符串值
|
||||
*/
|
||||
@TableField("str_v")
|
||||
private String strV;
|
||||
|
||||
/**
|
||||
* 长整型值
|
||||
*/
|
||||
@TableField("long_v")
|
||||
private Long longV;
|
||||
|
||||
/**
|
||||
* 双精度值
|
||||
*/
|
||||
@TableField("dbl_v")
|
||||
private Double dblV;
|
||||
|
||||
/**
|
||||
* JSON值
|
||||
*/
|
||||
@TableField("json_v")
|
||||
private String jsonV;
|
||||
|
||||
/**
|
||||
* 最后更新时间戳
|
||||
*/
|
||||
@TableField("last_update_ts")
|
||||
private Long lastUpdateTs;
|
||||
|
||||
/**
|
||||
* 版本号(用于乐观锁控制)
|
||||
*/
|
||||
@TableField
|
||||
private Integer version;
|
||||
|
||||
public AttributeKvEntry toData() {
|
||||
KvEntry kvEntry = null;
|
||||
if (strV != null) {
|
||||
kvEntry = new StringDataEntry(attrKey, strV);
|
||||
} else if (boolV != null) {
|
||||
kvEntry = new BooleanDataEntry(attrKey, boolV);
|
||||
} else if (dblV != null) {
|
||||
kvEntry = new DoubleDataEntry(attrKey, dblV);
|
||||
} else if (longV != null) {
|
||||
kvEntry = new LongDataEntry(attrKey, longV);
|
||||
} else if (jsonV != null) {
|
||||
kvEntry = new JsonDataEntry(attrKey, jsonV);
|
||||
}
|
||||
|
||||
return new BaseAttributeKvEntry(kvEntry, lastUpdateTs, version);
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,8 @@ 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 sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -25,7 +23,7 @@ import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_gun")
|
||||
@TableName("t_gun")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -36,28 +34,23 @@ public class Gun implements Serializable, HasVersion {
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
@NoXss
|
||||
private String gunNo;
|
||||
|
||||
@NoXss
|
||||
private String gunName;
|
||||
|
||||
@NoXss
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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 BigDecimal settlementAmount;
|
||||
|
||||
private JsonNode settlementDetails;
|
||||
|
||||
private BigDecimal electricityQuantity;
|
||||
|
||||
|
||||
}
|
||||
@@ -14,17 +14,16 @@ 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 sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@TableName(value = "jcpp_pile", autoResultMap = true)
|
||||
@TableName(value = "t_pile", autoResultMap = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -35,28 +34,30 @@ public class Pile implements Serializable, HasVersion {
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
@NoXss
|
||||
private String pileName;
|
||||
|
||||
@NoXss
|
||||
private String pileCode;
|
||||
|
||||
@NoXss
|
||||
private String protocol;
|
||||
|
||||
private UUID stationId;
|
||||
|
||||
private UUID ownerId;
|
||||
|
||||
private OwnerTypeEnum ownerType;
|
||||
|
||||
@NoXss
|
||||
private String brand;
|
||||
|
||||
@NoXss
|
||||
private String model;
|
||||
|
||||
@NoXss
|
||||
private String manufacturer;
|
||||
|
||||
private PileStatusEnum status;
|
||||
|
||||
private PileTypeEnum type;
|
||||
|
||||
private Integer version;
|
||||
|
||||
@@ -14,9 +14,8 @@ 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 sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -24,7 +23,7 @@ import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_station")
|
||||
@TableName("t_station")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -35,30 +34,32 @@ public class Station implements Serializable, HasVersion {
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
@NoXss
|
||||
private String stationName;
|
||||
|
||||
@NoXss
|
||||
private String stationCode;
|
||||
|
||||
private UUID ownerId;
|
||||
|
||||
private Float longitude;
|
||||
|
||||
private Float latitude;
|
||||
|
||||
private OwnerTypeEnum ownerType;
|
||||
|
||||
@NoXss
|
||||
private String province;
|
||||
|
||||
@NoXss
|
||||
private String city;
|
||||
|
||||
@NoXss
|
||||
private String county;
|
||||
|
||||
@NoXss
|
||||
private String address;
|
||||
|
||||
private StationStatusEnum status;
|
||||
|
||||
private Integer version;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package sanbing.jcpp.app.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -14,7 +15,10 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum;
|
||||
import sanbing.jcpp.app.dal.config.ibatis.typehandlers.UserCredentialsTypeHandler;
|
||||
import sanbing.jcpp.app.service.security.model.UserCredentials;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -23,24 +27,45 @@ import java.util.UUID;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("jcpp_user")
|
||||
@TableName("t_user")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class User implements Serializable, HasVersion {
|
||||
|
||||
public User(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User(User user) {
|
||||
this.id = user.getId();
|
||||
this.createdTime = user.getCreatedTime();
|
||||
this.updatedTime = user.getUpdatedTime();
|
||||
this.additionalInfo = user.getAdditionalInfo();
|
||||
this.status = user.getStatus();
|
||||
this.userName = user.getUserName();
|
||||
this.userCredentials = user.getUserCredentials();
|
||||
this.authority = user.getAuthority();
|
||||
this.version = user.getVersion();
|
||||
}
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private UUID id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
private JsonNode additionalInfo;
|
||||
|
||||
private UserStatusEnum status;
|
||||
|
||||
private String userName;
|
||||
|
||||
private JsonNode userCredentials;
|
||||
@TableField(typeHandler = UserCredentialsTypeHandler.class)
|
||||
private UserCredentials userCredentials;
|
||||
|
||||
private AuthorityEnum authority;
|
||||
|
||||
private Integer version;
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import sanbing.jcpp.app.dal.entity.Attribute;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 属性数据访问层
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Mapper
|
||||
public interface AttributeMapper extends BaseMapper<Attribute> {
|
||||
|
||||
/**
|
||||
* 查询实体的所有属性
|
||||
*/
|
||||
List<Attribute> findByEntity(@Param("entityId") UUID entityId);
|
||||
|
||||
/**
|
||||
* 查询实体的特定属性
|
||||
*/
|
||||
Attribute findByEntityAndKey(@Param("entityId") UUID entityId, @Param("attrKey") String attrKey);
|
||||
|
||||
/**
|
||||
* 查询实体在指定属性类型下的所有属性 (兼容原JPA方法)
|
||||
* 注意:此方法主要用于兼容性,实际t_attr表中没有attribute_type字段
|
||||
*/
|
||||
List<Attribute> findAllByEntityIdAndAttributeType(@Param("entityId") UUID entityId);
|
||||
|
||||
/**
|
||||
* 删除指定实体的指定属性
|
||||
*/
|
||||
void deleteByEntityIdAndKey(@Param("entityId") UUID entityId,
|
||||
@Param("attrKey") String attrKey);
|
||||
|
||||
/**
|
||||
* 根据实体ID和属性键列表查询属性
|
||||
*
|
||||
*/
|
||||
List<Attribute> findAllByIdAndAttrKey(UUID entityId, Collection<String> attrKeys);
|
||||
}
|
||||
@@ -7,10 +7,117 @@
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import sanbing.jcpp.app.adapter.request.GunQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.response.GunWithStatusResponse;
|
||||
import sanbing.jcpp.app.dal.entity.Gun;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface GunMapper extends BaseMapper<Gun> {
|
||||
|
||||
/**
|
||||
* 根据充电桩编码和充电枪编码查询充电枪
|
||||
*/
|
||||
Gun selectByPileCodeAndGunCode(@Param("pileCode") String pileCode, @Param("gunCode") String gunCode);
|
||||
|
||||
/**
|
||||
* 分页查询充电枪及其状态信息
|
||||
* 使用MyBatis XML配置,避免魔法值错误,提高SQL可读性和可维护性
|
||||
*/
|
||||
IPage<GunWithStatusResponse> selectGunWithStatusPage(Page<GunWithStatusResponse> page, @Param("request") GunQueryRequest request);
|
||||
|
||||
/**
|
||||
* 统计充电桩下的充电枪数量
|
||||
*
|
||||
* @param pileId 充电桩ID
|
||||
* @return 充电枪数量
|
||||
*/
|
||||
long countByPileId(@Param("pileId") UUID pileId);
|
||||
|
||||
/**
|
||||
* 统计空闲状态的充电枪数量 (IDLE)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 空闲充电枪数量
|
||||
*/
|
||||
long countIdleGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计已插枪未充电状态的充电枪数量 (INSERTED)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 已插枪未充电充电枪数量
|
||||
*/
|
||||
long countInsertedGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计充电中状态的充电枪数量 (CHARGING)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 充电中充电枪数量
|
||||
*/
|
||||
long countChargingGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计充电完成状态的充电枪数量 (CHARGE_COMPLETE)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 充电完成充电枪数量
|
||||
*/
|
||||
long countChargeCompleteGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计放电准备状态的充电枪数量 (DISCHARGE_READY)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 放电准备充电枪数量
|
||||
*/
|
||||
long countDischargeReadyGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计放电中状态的充电枪数量 (DISCHARGING)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 放电中充电枪数量
|
||||
*/
|
||||
long countDischargingGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计放电完成状态的充电枪数量 (DISCHARGE_COMPLETE)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 放电完成充电枪数量
|
||||
*/
|
||||
long countDischargeCompleteGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计预约状态的充电枪数量 (RESERVED)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 预约充电枪数量
|
||||
*/
|
||||
long countReservedGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
|
||||
/**
|
||||
* 统计故障状态的充电枪数量 (FAULT)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param status 状态值
|
||||
* @return 故障充电枪数量
|
||||
*/
|
||||
long countFaultGuns(@Param("statusKey") String statusKey, @Param("status") String status);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
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> {
|
||||
}
|
||||
@@ -7,19 +7,72 @@
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import sanbing.jcpp.app.adapter.request.PileQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.response.PileWithStatusResponse;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.app.data.kv.AttrKeyEnum;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface PileMapper extends BaseMapper<Pile> {
|
||||
|
||||
@Select("SELECT " +
|
||||
" * " +
|
||||
"FROM " +
|
||||
" jcpp_pile " +
|
||||
"WHERE " +
|
||||
" pile_code = #{pileCode}")
|
||||
/**
|
||||
* 根据充电桩编码查询充电桩
|
||||
*
|
||||
* @param pileCode 充电桩编码
|
||||
* @return 充电桩实体
|
||||
*/
|
||||
Pile selectByCode(String pileCode);
|
||||
|
||||
/**
|
||||
* 分页查询充电桩及其状态信息
|
||||
* 使用MyBatis XML配置,避免魔法值错误,提高SQL可读性和可维护性
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param request 查询请求参数
|
||||
* @param statusKey 状态属性键
|
||||
* @param connectedAtKey 连接时间属性键
|
||||
* @param disconnectedAtKey 断开连接时间属性键
|
||||
* @param lastActiveTimeKey 最后活跃时间属性键
|
||||
*/
|
||||
IPage<PileWithStatusResponse> selectPileWithStatusPage(
|
||||
Page<PileWithStatusResponse> page,
|
||||
@Param("request") PileQueryRequest request,
|
||||
@Param("statusKey") AttrKeyEnum statusKey,
|
||||
@Param("connectedAtKey") AttrKeyEnum connectedAtKey,
|
||||
@Param("disconnectedAtKey") AttrKeyEnum disconnectedAtKey,
|
||||
@Param("lastActiveTimeKey") AttrKeyEnum lastActiveTimeKey
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计充电站下的充电桩数量
|
||||
*
|
||||
* @param stationId 充电站ID
|
||||
* @return 充电桩数量
|
||||
*/
|
||||
long countByStationId(@Param("stationId") UUID stationId);
|
||||
|
||||
/**
|
||||
* 统计在线充电桩数量
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param onlineStatus 在线状态值
|
||||
* @return 在线充电桩数量
|
||||
*/
|
||||
long countOnlinePiles(@Param("statusKey") String statusKey, @Param("onlineStatus") String onlineStatus);
|
||||
|
||||
/**
|
||||
* 统计离线充电桩数量(包括未设置状态的)
|
||||
*
|
||||
* @param statusKey 状态属性键
|
||||
* @param offlineStatus 离线状态值
|
||||
* @return 离线充电桩数量
|
||||
*/
|
||||
long countOfflinePiles(@Param("statusKey") String statusKey, @Param("offlineStatus") String offlineStatus);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface StationMapper extends BaseMapper<Station> {
|
||||
}
|
||||
@@ -7,10 +7,24 @@
|
||||
package sanbing.jcpp.app.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import sanbing.jcpp.app.dal.entity.User;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
/**
|
||||
* 根据用户名查找用户(默认不区分大小写)
|
||||
*/
|
||||
@Select("SELECT * FROM t_user WHERE LOWER(user_name) = LOWER(#{userName})")
|
||||
User findByUserName(@Param("userName") String userName);
|
||||
|
||||
/**
|
||||
* 检查用户名是否已存在(默认不区分大小写)
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM t_user WHERE LOWER(user_name) = LOWER(#{userName})")
|
||||
int countByUserName(@Param("userName") String userName);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository;
|
||||
|
||||
import sanbing.jcpp.app.dal.entity.Gun;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电枪数据访问接口
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface GunRepository {
|
||||
|
||||
/**
|
||||
* 根据充电桩编码和充电枪编码查询充电枪
|
||||
*
|
||||
* @param pileCode 充电桩编码
|
||||
* @param gunCode 充电枪编码
|
||||
* @return 充电枪实体,如果不存在返回null
|
||||
*/
|
||||
Gun findByPileCodeAndGunCode(String pileCode, String gunCode);
|
||||
|
||||
/**
|
||||
* 根据充电枪ID查询充电枪
|
||||
*
|
||||
* @param gunId 充电枪ID
|
||||
* @return 充电枪实体,如果不存在返回null
|
||||
*/
|
||||
Gun findById(UUID gunId);
|
||||
}
|
||||
@@ -4,12 +4,12 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
package sanbing.jcpp.app.dal.repository;
|
||||
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
public interface PileRepository {
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.attribute;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
import org.springframework.jdbc.core.SqlProvider;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import sanbing.jcpp.app.dal.entity.Attribute;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Repository
|
||||
public class AttributeKvInsertRepository {
|
||||
|
||||
private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE)));
|
||||
private static final String EMPTY_STR = "";
|
||||
|
||||
@Value("${sql.remove_null_chars:true}")
|
||||
private boolean removeNullChars;
|
||||
|
||||
@Resource
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Resource
|
||||
protected TransactionTemplate transactionTemplate;
|
||||
|
||||
private static final String BATCH_UPDATE = "UPDATE t_attr SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?, version = nextval('attr_kv_version_seq') " +
|
||||
"WHERE entity_id = ? and attr_key = ? RETURNING version;";
|
||||
|
||||
private static final String INSERT_OR_UPDATE =
|
||||
"INSERT INTO t_attr (entity_id, attr_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts, version) " +
|
||||
"VALUES(?, ?, ?, ?, ?, ?, cast(? AS json), ?, nextval('attr_kv_version_seq')) " +
|
||||
"ON CONFLICT (entity_id, attr_key) " +
|
||||
"DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?, version = nextval('attr_kv_version_seq') RETURNING version;";
|
||||
|
||||
// 合并自 AbstractInsertRepository 的方法
|
||||
protected String replaceNullChars(String strValue) {
|
||||
if (removeNullChars && strValue != null) {
|
||||
return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR);
|
||||
}
|
||||
return strValue;
|
||||
}
|
||||
|
||||
// 合并自 AbstractVersionedInsertRepository 的方法
|
||||
public List<Integer> saveOrUpdate(List<Attribute> entities) {
|
||||
return transactionTemplate.execute(status -> {
|
||||
List<Integer> seqNumbers = new ArrayList<>(entities.size());
|
||||
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
|
||||
int[] updateResult = onBatchUpdate(entities, keyHolder);
|
||||
|
||||
List<Map<String, Object>> seqNumbersList = keyHolder.getKeyList();
|
||||
|
||||
int notUpdatedCount = entities.size() - seqNumbersList.size();
|
||||
|
||||
List<Integer> toInsertIndexes = new ArrayList<>(notUpdatedCount);
|
||||
List<Attribute> insertEntities = new ArrayList<>(notUpdatedCount);
|
||||
for (int i = 0, keyHolderIndex = 0; i < updateResult.length; i++) {
|
||||
if (updateResult[i] == 0) {
|
||||
insertEntities.add(entities.get(i));
|
||||
seqNumbers.add(null);
|
||||
toInsertIndexes.add(i);
|
||||
} else {
|
||||
seqNumbers.add((Integer) seqNumbersList.get(keyHolderIndex).get("version"));
|
||||
keyHolderIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertEntities.isEmpty()) {
|
||||
return seqNumbers;
|
||||
}
|
||||
|
||||
int[] insertResult = onInsertOrUpdate(insertEntities, keyHolder);
|
||||
|
||||
seqNumbersList = keyHolder.getKeyList();
|
||||
|
||||
for (int i = 0, keyHolderIndex = 0; i < insertResult.length; i++) {
|
||||
if (insertResult[i] != 0) {
|
||||
seqNumbers.set(toInsertIndexes.get(i), (Integer) seqNumbersList.get(keyHolderIndex).get("version"));
|
||||
keyHolderIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return seqNumbers;
|
||||
});
|
||||
}
|
||||
|
||||
private int[] onBatchUpdate(List<Attribute> entities, KeyHolder keyHolder) {
|
||||
return jdbcTemplate.batchUpdate(new SequencePreparedStatementCreator(getBatchUpdateQuery()), new BatchPreparedStatementSetter() {
|
||||
@Override
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException {
|
||||
setOnBatchUpdateValues(ps, i, entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return entities.size();
|
||||
}
|
||||
}, keyHolder);
|
||||
}
|
||||
|
||||
private int[] onInsertOrUpdate(List<Attribute> insertEntities, KeyHolder keyHolder) {
|
||||
return jdbcTemplate.batchUpdate(new SequencePreparedStatementCreator(getInsertOrUpdateQuery()), new BatchPreparedStatementSetter() {
|
||||
@Override
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException {
|
||||
setOnInsertOrUpdateValues(ps, i, insertEntities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return insertEntities.size();
|
||||
}
|
||||
}, keyHolder);
|
||||
}
|
||||
|
||||
protected void setOnBatchUpdateValues(PreparedStatement ps, int i, List<Attribute> entities) throws SQLException {
|
||||
Attribute kvEntity = entities.get(i);
|
||||
ps.setString(1, replaceNullChars(kvEntity.getStrV()));
|
||||
|
||||
if (kvEntity.getLongV() != null) {
|
||||
ps.setLong(2, kvEntity.getLongV());
|
||||
} else {
|
||||
ps.setNull(2, Types.BIGINT);
|
||||
}
|
||||
|
||||
if (kvEntity.getDblV() != null) {
|
||||
ps.setDouble(3, kvEntity.getDblV());
|
||||
} else {
|
||||
ps.setNull(3, Types.DOUBLE);
|
||||
}
|
||||
|
||||
if (kvEntity.getBoolV() != null) {
|
||||
ps.setBoolean(4, kvEntity.getBoolV());
|
||||
} else {
|
||||
ps.setNull(4, Types.BOOLEAN);
|
||||
}
|
||||
|
||||
ps.setString(5, replaceNullChars(kvEntity.getJsonV()));
|
||||
|
||||
ps.setLong(6, kvEntity.getLastUpdateTs());
|
||||
ps.setObject(7, kvEntity.getEntityId());
|
||||
ps.setString(8, kvEntity.getAttrKey());
|
||||
}
|
||||
|
||||
protected void setOnInsertOrUpdateValues(PreparedStatement ps, int i, List<Attribute> insertEntities) throws SQLException {
|
||||
Attribute kvEntity = insertEntities.get(i);
|
||||
ps.setObject(1, kvEntity.getEntityId());
|
||||
ps.setString(2, kvEntity.getAttrKey());
|
||||
|
||||
ps.setString(3, replaceNullChars(kvEntity.getStrV()));
|
||||
ps.setString(9, replaceNullChars(kvEntity.getStrV()));
|
||||
|
||||
if (kvEntity.getLongV() != null) {
|
||||
ps.setLong(4, kvEntity.getLongV());
|
||||
ps.setLong(10, kvEntity.getLongV());
|
||||
} else {
|
||||
ps.setNull(4, Types.BIGINT);
|
||||
ps.setNull(10, Types.BIGINT);
|
||||
}
|
||||
|
||||
if (kvEntity.getDblV() != null) {
|
||||
ps.setDouble(5, kvEntity.getDblV());
|
||||
ps.setDouble(11, kvEntity.getDblV());
|
||||
} else {
|
||||
ps.setNull(5, Types.DOUBLE);
|
||||
ps.setNull(11, Types.DOUBLE);
|
||||
}
|
||||
|
||||
if (kvEntity.getBoolV() != null) {
|
||||
ps.setBoolean(6, kvEntity.getBoolV());
|
||||
ps.setBoolean(12, kvEntity.getBoolV());
|
||||
} else {
|
||||
ps.setNull(6, Types.BOOLEAN);
|
||||
ps.setNull(12, Types.BOOLEAN);
|
||||
}
|
||||
|
||||
ps.setString(7, replaceNullChars(kvEntity.getJsonV()));
|
||||
ps.setString(13, replaceNullChars(kvEntity.getJsonV()));
|
||||
|
||||
ps.setLong(8, kvEntity.getLastUpdateTs());
|
||||
ps.setLong(14, kvEntity.getLastUpdateTs());
|
||||
}
|
||||
|
||||
protected String getBatchUpdateQuery() {
|
||||
return BATCH_UPDATE;
|
||||
}
|
||||
|
||||
protected String getInsertOrUpdateQuery() {
|
||||
return INSERT_OR_UPDATE;
|
||||
}
|
||||
|
||||
private record SequencePreparedStatementCreator(String sql) implements PreparedStatementCreator, SqlProvider {
|
||||
|
||||
private static final String[] COLUMNS = {"version"};
|
||||
|
||||
@Override
|
||||
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
|
||||
return con.prepareStatement(sql, COLUMNS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql() {
|
||||
return this.sql;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.attribute;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import sanbing.jcpp.app.data.kv.AttributeKvEntry;
|
||||
import sanbing.jcpp.infrastructure.util.JCPPPair;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface AttributeRepository {
|
||||
|
||||
Optional<AttributeKvEntry> find(UUID entityId, String attrKey);
|
||||
|
||||
List<AttributeKvEntry> find(UUID entityId, Collection<String> attrKeys);
|
||||
|
||||
List<AttributeKvEntry> findAll( UUID entityId);
|
||||
|
||||
ListenableFuture<Integer> save(UUID entityId, AttributeKvEntry attribute);
|
||||
|
||||
List<ListenableFuture<String>> removeAll(UUID entityId, List<String> keys);
|
||||
|
||||
List<ListenableFuture<JCPPPair<String, Integer>>> removeAllWithVersions(UUID entityId, List<String> keys);
|
||||
|
||||
List<String> removeAllByEntityId(UUID entityId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.attribute;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import sanbing.jcpp.app.dal.entity.Attribute;
|
||||
import sanbing.jcpp.app.dal.mapper.AttributeMapper;
|
||||
import sanbing.jcpp.app.dal.repository.batch.ScheduledLogExecutorComponent;
|
||||
import sanbing.jcpp.app.dal.repository.batch.SqlBlockingQueueParams;
|
||||
import sanbing.jcpp.app.dal.repository.batch.SqlBlockingQueueWrapper;
|
||||
import sanbing.jcpp.app.dal.repository.impl.RepositoryExecutorService;
|
||||
import sanbing.jcpp.app.data.kv.AttributeKvEntry;
|
||||
import sanbing.jcpp.infrastructure.stats.StatsFactory;
|
||||
import sanbing.jcpp.infrastructure.util.JCPPPair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultAttributeRepository implements AttributeRepository {
|
||||
|
||||
@Resource
|
||||
protected RepositoryExecutorService service;
|
||||
|
||||
@Resource
|
||||
protected JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Resource
|
||||
protected TransactionTemplate transactionTemplate;
|
||||
|
||||
@Resource
|
||||
ScheduledLogExecutorComponent logExecutor;
|
||||
|
||||
@Resource
|
||||
private AttributeMapper attributeMapper;
|
||||
|
||||
@Resource
|
||||
private AttributeKvInsertRepository attributeKvInsertRepository;
|
||||
|
||||
@Resource
|
||||
private StatsFactory statsFactory;
|
||||
|
||||
@Value("${sql.attributes.batch_size:1000}")
|
||||
private int batchSize;
|
||||
|
||||
@Value("${sql.attributes.batch_max_delay:100}")
|
||||
private long maxDelay;
|
||||
|
||||
@Value("${sql.attributes.stats_print_interval_ms:1000}")
|
||||
private long statsPrintIntervalMs;
|
||||
|
||||
@Value("${sql.attributes.batch_threads:4}")
|
||||
private int batchThreads;
|
||||
|
||||
@Value("${sql.batch_sort:true}")
|
||||
private boolean batchSortEnabled;
|
||||
|
||||
private SqlBlockingQueueWrapper<Attribute, Integer> queue;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
SqlBlockingQueueParams params = SqlBlockingQueueParams.builder()
|
||||
.logName("Attributes")
|
||||
.batchSize(batchSize)
|
||||
.maxDelay(maxDelay)
|
||||
.statsPrintIntervalMs(statsPrintIntervalMs)
|
||||
.statsNamePrefix("attributes")
|
||||
.batchSortEnabled(batchSortEnabled)
|
||||
.withResponse(true)
|
||||
.build();
|
||||
|
||||
Function<Attribute, Integer> hashcodeFunction = entity -> entity.getEntityId().hashCode();
|
||||
queue = new SqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory);
|
||||
queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v),
|
||||
Comparator.comparing(Attribute::getEntityId)
|
||||
.thenComparing(Attribute::getAttrKey), l -> l
|
||||
);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void destroy() {
|
||||
if (queue != null) {
|
||||
queue.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AttributeKvEntry> find(UUID entityId, String attrKey) {
|
||||
Attribute attributeKvEntity = attributeMapper.findByEntityAndKey(entityId, attrKey);
|
||||
if (attributeKvEntity != null) {
|
||||
return Optional.ofNullable(attributeKvEntity.toData());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AttributeKvEntry> find(UUID entityId, Collection<String> attrKeys) {
|
||||
List<Attribute> attributes = attributeMapper.findAllByIdAndAttrKey(entityId, attrKeys);
|
||||
return convertDataList(Lists.newArrayList(attributes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AttributeKvEntry> findAll(UUID entityId) {
|
||||
List<Attribute> attributes = attributeMapper.findAllByEntityIdAndAttributeType(
|
||||
entityId);
|
||||
return convertDataList(Lists.newArrayList(attributes));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Integer> save(UUID entityId, AttributeKvEntry attribute) {
|
||||
Attribute entity = new Attribute();
|
||||
entity.setEntityId(entityId);
|
||||
entity.setAttrKey(attribute.getKey());
|
||||
entity.setLastUpdateTs(attribute.getLastUpdateTs());
|
||||
entity.setStrV(attribute.getStrValue().orElse(null));
|
||||
entity.setDblV(attribute.getDoubleValue().orElse(null));
|
||||
entity.setLongV(attribute.getLongValue().orElse(null));
|
||||
entity.setBoolV(attribute.getBooleanValue().orElse(null));
|
||||
entity.setJsonV(attribute.getJsonValue().orElse(null));
|
||||
return addToQueue(entity);
|
||||
}
|
||||
|
||||
private ListenableFuture<Integer> addToQueue(Attribute entity) {
|
||||
return queue.add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ListenableFuture<String>> removeAll(UUID entityId, List<String> keys) {
|
||||
List<ListenableFuture<String>> futuresList = new ArrayList<>(keys.size());
|
||||
for (String key : keys) {
|
||||
futuresList.add(service.submit(() -> {
|
||||
attributeMapper.deleteByEntityIdAndKey(entityId, key);
|
||||
return key;
|
||||
}));
|
||||
}
|
||||
return futuresList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ListenableFuture<JCPPPair<String, Integer>>> removeAllWithVersions(UUID entityId, List<String> keys) {
|
||||
List<ListenableFuture<JCPPPair<String, Integer>>> futuresList = new ArrayList<>(keys.size());
|
||||
for (String key : keys) {
|
||||
futuresList.add(service.submit(() -> {
|
||||
Integer version = transactionTemplate.execute(status -> jdbcTemplate.query("DELETE FROM t_attr WHERE entity_id = ? " +
|
||||
"AND attr_key = ? RETURNING nextval('attr_kv_version_seq')",
|
||||
rs -> rs.next() ? rs.getInt(1) : null, entityId, key));
|
||||
return JCPPPair.of(key, version);
|
||||
}));
|
||||
}
|
||||
return futuresList;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public List<String> removeAllByEntityId(UUID entityId) {
|
||||
return jdbcTemplate.queryForList("DELETE FROM t_attr WHERE entity_id = ? " +
|
||||
"RETURNING attr_key", entityId).stream()
|
||||
.map(row -> row.get("attr_key").toString())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<AttributeKvEntry> convertDataList(Collection<Attribute> toConvert) {
|
||||
if (CollectionUtils.isEmpty(toConvert)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AttributeKvEntry> converted = new ArrayList<>(toConvert.size());
|
||||
for (Attribute attribute : toConvert) {
|
||||
if (attribute != null) {
|
||||
converted.add(attribute.toData());
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.attribute;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import sanbing.jcpp.app.data.kv.AttributeKvEntry;
|
||||
import sanbing.jcpp.app.data.kv.KvEntry;
|
||||
import sanbing.jcpp.infrastructure.util.exception.DataValidationException;
|
||||
import sanbing.jcpp.infrastructure.util.exception.IncorrectParameterException;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXssValidator;
|
||||
import sanbing.jcpp.infrastructure.util.validation.Validator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class KvValidator {
|
||||
|
||||
private static final Cache<String, Boolean> validatedKeys;
|
||||
|
||||
static {
|
||||
validatedKeys = Caffeine.newBuilder()
|
||||
.expireAfterAccess(24, TimeUnit.HOURS)
|
||||
.maximumSize(50000).build();
|
||||
}
|
||||
|
||||
public static void validate(List<? extends KvEntry> tsKvEntries, boolean valueNoXssValidation) {
|
||||
tsKvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation));
|
||||
}
|
||||
|
||||
public static void validate(KvEntry tsKvEntry, boolean valueNoXssValidation) {
|
||||
if (tsKvEntry == null) {
|
||||
throw new IncorrectParameterException("键值条目不能为空");
|
||||
}
|
||||
|
||||
String key = tsKvEntry.getKey();
|
||||
|
||||
if (StringUtils.isBlank(key)) {
|
||||
throw new DataValidationException("键不能为空");
|
||||
}
|
||||
|
||||
if (key.length() > 255) {
|
||||
throw new DataValidationException("验证错误:键的长度必须小于或等于255");
|
||||
}
|
||||
|
||||
Boolean isValid = validatedKeys.asMap().get(key);
|
||||
if (isValid == null) {
|
||||
isValid = NoXssValidator.isValid(key);
|
||||
validatedKeys.put(key, isValid);
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new DataValidationException("验证错误:键的格式不正确");
|
||||
}
|
||||
|
||||
if (valueNoXssValidation) {
|
||||
Object value = tsKvEntry.getValue();
|
||||
if (value instanceof CharSequence || value instanceof JsonNode) {
|
||||
if (!NoXssValidator.isValid(value.toString())) {
|
||||
throw new DataValidationException("验证错误:值的格式不正确");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void validateId(UUID id) {
|
||||
Validator.validateId(id, uuid -> "ID不正确: " + uuid);
|
||||
}
|
||||
|
||||
public static void validateAttributeList(List<AttributeKvEntry> kvEntries, boolean valueNoXssValidation) {
|
||||
kvEntries.forEach(tsKvEntry -> validateAttribute(tsKvEntry, valueNoXssValidation));
|
||||
}
|
||||
|
||||
public static void validateAttribute(AttributeKvEntry kvEntry, boolean valueNoXssValidation) {
|
||||
validate(kvEntry, valueNoXssValidation);
|
||||
if (kvEntry.getDataType() == null) {
|
||||
throw new IncorrectParameterException("键值条目的数据类型不能为空");
|
||||
} else {
|
||||
Validator.validateString(kvEntry.getKey(), "键值条目错误:键不能为空");
|
||||
Validator.validatePositiveNumber(kvEntry.getLastUpdateTs(), "最后更新时间戳错误:时间戳必须为正数");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class ScheduledLogExecutorComponent {
|
||||
|
||||
private ScheduledExecutorService schedulerLogExecutor;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
schedulerLogExecutor = Executors.newSingleThreadScheduledExecutor(
|
||||
JCPPThreadFactory.forName("sql-log-%d")
|
||||
);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
if (schedulerLogExecutor != null) {
|
||||
schedulerLogExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
|
||||
schedulerLogExecutor.scheduleAtFixedRate(command, initialDelay, period, unit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.stats.MessagesStats;
|
||||
import sanbing.jcpp.infrastructure.util.CollectionsUtil;
|
||||
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class SqlBlockingQueue<E, R> implements SqlQueue<E, R> {
|
||||
|
||||
private final BlockingQueue<SqlQueueElement<E, R>> queue = new LinkedBlockingQueue<>();
|
||||
private final SqlBlockingQueueParams params;
|
||||
|
||||
private ExecutorService executor;
|
||||
private final MessagesStats stats;
|
||||
|
||||
public SqlBlockingQueue(SqlBlockingQueueParams params, MessagesStats stats) {
|
||||
this.params = params;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ScheduledLogExecutorComponent logExecutor, Function<List<E>, List<R>> saveFunction, Comparator<E> batchUpdateComparator, Function<List<SqlQueueElement<E, R>>, List<SqlQueueElement<E, R>>> filter, int index) {
|
||||
executor = Executors.newSingleThreadExecutor(JCPPThreadFactory.forName("sql-queue-" + index + "-" + params.getLogName().toLowerCase()));
|
||||
executor.submit(() -> {
|
||||
String logName = params.getLogName();
|
||||
int batchSize = params.getBatchSize();
|
||||
long maxDelay = params.getMaxDelay();
|
||||
final List<SqlQueueElement<E, R>> entities = new ArrayList<>(batchSize);
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
long currentTs = System.currentTimeMillis();
|
||||
SqlQueueElement<E, R> attr = queue.poll(maxDelay, TimeUnit.MILLISECONDS);
|
||||
if (attr == null) {
|
||||
continue;
|
||||
} else {
|
||||
entities.add(attr);
|
||||
}
|
||||
queue.drainTo(entities, batchSize - 1);
|
||||
boolean fullPack = entities.size() == batchSize;
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[{}] Going to save {} entities", logName, entities.size());
|
||||
log.trace("[{}] Going to save entities: {}", logName, entities);
|
||||
}
|
||||
|
||||
List<SqlQueueElement<E, R>> entitiesToSave = filter.apply(entities);
|
||||
|
||||
if (params.isBatchSortEnabled()) {
|
||||
entitiesToSave = entitiesToSave.stream().sorted((o1, o2) -> batchUpdateComparator.compare(o1.entity(), o2.entity())).toList();
|
||||
}
|
||||
|
||||
List<R> result = saveFunction.apply(entitiesToSave.stream().map(SqlQueueElement::entity).collect(Collectors.toList()));
|
||||
|
||||
if (params.isWithResponse()) {
|
||||
for (int i = 0; i < entitiesToSave.size(); i++) {
|
||||
entitiesToSave.get(i).future().set(result.get(i));
|
||||
}
|
||||
|
||||
if (entities.size() > entitiesToSave.size()) {
|
||||
CollectionsUtil.diffLists(entitiesToSave, entities).forEach(v -> v.future().set(null));
|
||||
}
|
||||
} else {
|
||||
entities.forEach(v -> v.future().set(null));
|
||||
}
|
||||
|
||||
stats.incrementSuccessful(entities.size());
|
||||
if (!fullPack) {
|
||||
long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs);
|
||||
if (remainingDelay > 0) {
|
||||
Thread.sleep(remainingDelay);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof InterruptedException) {
|
||||
log.info("[{}] Queue polling was interrupted", logName);
|
||||
break;
|
||||
} else {
|
||||
log.error("[{}] Failed to save {} entities", logName, entities.size(), t);
|
||||
try {
|
||||
stats.incrementFailed(entities.size());
|
||||
entities.forEach(entityFutureWrapper -> entityFutureWrapper.future().setException(t));
|
||||
} catch (Throwable th) {
|
||||
log.error("[{}] Failed to set future exception", logName, th);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
entities.clear();
|
||||
}
|
||||
}
|
||||
log.info("[{}] Queue polling completed", logName);
|
||||
});
|
||||
|
||||
logExecutor.scheduleAtFixedRate(() -> {
|
||||
if (!queue.isEmpty() || stats.getTotal() > 0 || stats.getSuccessful() > 0 || stats.getFailed() > 0) {
|
||||
log.info("Queue-{} [{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", index,
|
||||
params.getLogName(), queue.size(), stats.getTotal(), stats.getSuccessful(), stats.getFailed());
|
||||
stats.reset();
|
||||
}
|
||||
}, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<R> add(E element) {
|
||||
SettableFuture<R> future = SettableFuture.create();
|
||||
queue.add(new SqlQueueElement<>(future, element));
|
||||
stats.incrementTotal();
|
||||
return future;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@Builder
|
||||
public class SqlBlockingQueueParams {
|
||||
|
||||
private final String logName;
|
||||
private final int batchSize;
|
||||
private final long maxDelay;
|
||||
private final long statsPrintIntervalMs;
|
||||
private final String statsNamePrefix;
|
||||
private final boolean batchSortEnabled;
|
||||
private final boolean withResponse;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.stats.MessagesStats;
|
||||
import sanbing.jcpp.infrastructure.stats.StatsFactory;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
public class SqlBlockingQueueWrapper<E, R> {
|
||||
private final CopyOnWriteArrayList<SqlBlockingQueue<E, R>> queues = new CopyOnWriteArrayList<>();
|
||||
private final SqlBlockingQueueParams params;
|
||||
private final Function<E, Integer> hashCodeFunction;
|
||||
private final int maxThreads;
|
||||
private final StatsFactory statsFactory;
|
||||
|
||||
/**
|
||||
* Starts JCPPSqlBlockingQueues.
|
||||
*
|
||||
* @param logExecutor executor that will be printing logs and statistics
|
||||
* @param saveFunction function to save entities in database
|
||||
* @param batchUpdateComparator comparator to sort entities by primary key to avoid deadlocks in cluster mode
|
||||
* NOTE: you must use all of primary key parts in your comparator
|
||||
*/
|
||||
public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction, Comparator<E> batchUpdateComparator) {
|
||||
init(logExecutor, l -> { saveFunction.accept(l); return null; }, batchUpdateComparator, l -> l);
|
||||
}
|
||||
|
||||
public void init(ScheduledLogExecutorComponent logExecutor, Function<List<E>, List<R>> saveFunction, Comparator<E> batchUpdateComparator, Function<List<SqlQueueElement<E, R>>, List<SqlQueueElement<E, R>>> filter) {
|
||||
for (int i = 0; i < maxThreads; i++) {
|
||||
MessagesStats stats = statsFactory.createMessagesStats(params.getStatsNamePrefix() + ".queue." + i);
|
||||
SqlBlockingQueue<E, R> queue = new SqlBlockingQueue<>(params, stats);
|
||||
queues.add(queue);
|
||||
queue.init(logExecutor, saveFunction, batchUpdateComparator, filter, i);
|
||||
}
|
||||
}
|
||||
|
||||
public ListenableFuture<R> add(E element) {
|
||||
int queueIndex = element != null ? (hashCodeFunction.apply(element) & 0x7FFFFFFF) % maxThreads : 0;
|
||||
return queues.get(queueIndex).add(element);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
queues.forEach(SqlBlockingQueue::destroy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface SqlQueue<E, R> {
|
||||
|
||||
void init(ScheduledLogExecutorComponent logExecutor, Function<List<E>, List<R>> saveFunction, Comparator<E> batchUpdateComparator, Function<List<SqlQueueElement<E, R>>, List<SqlQueueElement<E, R>>> filter, int queueIndex);
|
||||
|
||||
void destroy();
|
||||
|
||||
ListenableFuture<R> add(E element);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.batch;
|
||||
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
public record SqlQueueElement<E, R>(SettableFuture<R> future, E entity) {
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import sanbing.jcpp.infrastructure.cache.TransactionalCache;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class AbstractCachedEntityRepository<K extends Serializable, V extends Serializable, E> extends AbstractEntityRepository {
|
||||
|
||||
@Autowired
|
||||
protected TransactionalCache<K, V> cache;
|
||||
|
||||
protected void publishEvictEvent(E event) {
|
||||
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
eventPublisher.publishEvent(event);
|
||||
@@ -4,7 +4,7 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -4,9 +4,9 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCache;
|
||||
import sanbing.jcpp.infrastructure.cache.VersionedCacheKey;
|
||||
@@ -15,7 +15,7 @@ import java.io.Serializable;
|
||||
|
||||
public abstract class CachedVersionedEntityRepository<K extends VersionedCacheKey, V extends Serializable & HasVersion, E> extends AbstractCachedEntityRepository<K, V, E> {
|
||||
|
||||
@Resource
|
||||
@Autowired
|
||||
protected VersionedCache<K, V> cache;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
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.Gun;
|
||||
import sanbing.jcpp.app.dal.mapper.GunMapper;
|
||||
import sanbing.jcpp.app.dal.repository.GunRepository;
|
||||
import sanbing.jcpp.app.service.cache.gun.GunCacheEvictEvent;
|
||||
import sanbing.jcpp.app.service.cache.gun.GunCacheKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static sanbing.jcpp.infrastructure.util.validation.Validator.validateId;
|
||||
import static sanbing.jcpp.infrastructure.util.validation.Validator.validateString;
|
||||
|
||||
/**
|
||||
* 充电枪数据访问实现
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Repository
|
||||
@Slf4j
|
||||
public class GunRepositoryImpl extends CachedVersionedEntityRepository<GunCacheKey, Gun, GunCacheEvictEvent> implements GunRepository {
|
||||
|
||||
@Resource
|
||||
GunMapper gunMapper;
|
||||
|
||||
@TransactionalEventListener(classes = GunCacheEvictEvent.class)
|
||||
@Override
|
||||
public void handleEvictEvent(GunCacheEvictEvent event) {
|
||||
// 如果修改或删除充电枪,需要在这里消费删除事件
|
||||
List<GunCacheKey> toEvict = new ArrayList<>(3);
|
||||
|
||||
// 基于gunId的缓存key
|
||||
if (event.getGunId() != null) {
|
||||
toEvict.add(new GunCacheKey(event.getGunId()));
|
||||
}
|
||||
|
||||
// 基于pileCode+gunCode的缓存key
|
||||
if (event.getPileCode() != null && event.getGunCode() != null) {
|
||||
toEvict.add(new GunCacheKey(event.getPileCode(), event.getGunCode()));
|
||||
}
|
||||
|
||||
cache.evict(toEvict);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gun findByPileCodeAndGunCode(String pileCode, String gunCode) {
|
||||
validateString(pileCode, code -> "无效的桩编号: " + pileCode);
|
||||
validateString(gunCode, code -> "无效的枪编号: " + gunCode);
|
||||
|
||||
return cache.get(new GunCacheKey(pileCode, gunCode),
|
||||
() -> gunMapper.selectByPileCodeAndGunCode(pileCode, gunCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gun findById(UUID gunId) {
|
||||
validateId(gunId, id -> "无效的充电枪ID: " + gunId);
|
||||
|
||||
return cache.get(new GunCacheKey(gunId),
|
||||
() -> gunMapper.selectById(gunId));
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.repository;
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -12,6 +12,7 @@ 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.dal.repository.PileRepository;
|
||||
import sanbing.jcpp.app.service.cache.pile.PileCacheEvictEvent;
|
||||
import sanbing.jcpp.app.service.cache.pile.PileCacheKey;
|
||||
|
||||
@@ -21,7 +22,7 @@ import java.util.List;
|
||||
import static sanbing.jcpp.infrastructure.util.validation.Validator.validateString;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
@Repository
|
||||
@Slf4j
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.dal.repository.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import sanbing.jcpp.infrastructure.util.async.AbstractListeningExecutor;
|
||||
|
||||
@Component
|
||||
public class RepositoryExecutorService extends AbstractListeningExecutor {
|
||||
|
||||
@Value("${spring.datasource.hikari.maximum-pool-size}")
|
||||
private int poolSize;
|
||||
|
||||
@Override
|
||||
protected int getThreadPollSize() {
|
||||
return poolSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据库安装模式枚举
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Getter
|
||||
public enum InstallModeEnum {
|
||||
|
||||
/**
|
||||
* 初始化数据库,执行schema-init.sql并加载演示数据
|
||||
*/
|
||||
INIT("init", "初始化数据库"),
|
||||
|
||||
/**
|
||||
* 升级数据库,根据版本执行升级脚本
|
||||
*/
|
||||
UPGRADE("upgrade", "升级数据库"),
|
||||
|
||||
/**
|
||||
* 不执行任何操作
|
||||
*/
|
||||
DISABLED("disabled", "禁用安装功能");
|
||||
|
||||
private final String mode;
|
||||
private final String description;
|
||||
|
||||
InstallModeEnum(String mode, String description) {
|
||||
this.mode = mode;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据mode字符串获取枚举值
|
||||
*/
|
||||
public static InstallModeEnum fromMode(String mode) {
|
||||
if (mode == null || mode.isEmpty()) {
|
||||
return DISABLED;
|
||||
}
|
||||
|
||||
for (InstallModeEnum installMode : values()) {
|
||||
if (installMode.mode.equals(mode)) {
|
||||
return installMode;
|
||||
}
|
||||
}
|
||||
|
||||
return DISABLED;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author baigod
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
public class PileSession implements Serializable {
|
||||
@@ -37,6 +37,8 @@ public class PileSession implements Serializable {
|
||||
|
||||
private int nodeGrpcPort;
|
||||
|
||||
|
||||
|
||||
public PileSession(UUID pileId, String pileCode, String protocolName) {
|
||||
this.pileId = pileId;
|
||||
this.pileCode = pileCode;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* 属性键枚举,定义系统内置的属性键
|
||||
* 使用String类型提高可读性
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
public enum AttrKeyEnum {
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
STATUS( "status"),
|
||||
|
||||
/**
|
||||
* 连接时间
|
||||
*/
|
||||
CONNECTED_AT("connectedAt"),
|
||||
|
||||
/**
|
||||
* 断开连接时间
|
||||
*/
|
||||
DISCONNECTED_AT("disconnectedAt"),
|
||||
|
||||
/**
|
||||
* 最后活跃时间
|
||||
*/
|
||||
LAST_ACTIVE_TIME("lastActiveTime"),
|
||||
|
||||
/**
|
||||
* 充电枪运行状态
|
||||
*/
|
||||
GUN_RUN_STATUS("gunRunStatus"),
|
||||
|
||||
/**
|
||||
* 地锁状态
|
||||
*/
|
||||
LOCK_STATUS("lockStatus"),
|
||||
|
||||
/**
|
||||
* 车位状态
|
||||
*/
|
||||
PARK_STATUS("parkStatus");
|
||||
|
||||
@JsonValue
|
||||
private final String code;
|
||||
|
||||
AttrKeyEnum( String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
|
||||
import sanbing.jcpp.infrastructure.cache.HasVersion;
|
||||
|
||||
|
||||
public interface AttributeKvEntry extends KvEntry, HasVersion {
|
||||
|
||||
long getLastUpdateTs();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public record AttributesSaveResult(List<Integer> versions) {
|
||||
|
||||
public static final AttributesSaveResult EMPTY = new AttributesSaveResult(Collections.emptyList());
|
||||
|
||||
public static AttributesSaveResult of(List<Integer> versions) {
|
||||
if (versions == null) {
|
||||
return EMPTY;
|
||||
}
|
||||
return new AttributesSaveResult(versions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
public class BaseAttributeKvEntry implements AttributeKvEntry {
|
||||
|
||||
private static final long serialVersionUID = -6460767583563159407L;
|
||||
|
||||
private final long lastUpdateTs;
|
||||
@Valid
|
||||
private final KvEntry kv;
|
||||
|
||||
private final Integer version;
|
||||
|
||||
public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) {
|
||||
this.kv = kv;
|
||||
this.lastUpdateTs = lastUpdateTs;
|
||||
this.version = null;
|
||||
}
|
||||
|
||||
public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs, Integer version) {
|
||||
this.kv = kv;
|
||||
this.lastUpdateTs = lastUpdateTs;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public BaseAttributeKvEntry(long lastUpdateTs, KvEntry kv) {
|
||||
this(kv, lastUpdateTs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return kv.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType getDataType() {
|
||||
return kv.getDataType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getStrValue() {
|
||||
return kv.getStrValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getLongValue() {
|
||||
return kv.getLongValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> getBooleanValue() {
|
||||
return kv.getBooleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getDoubleValue() {
|
||||
return kv.getDoubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getJsonValue() {
|
||||
return kv.getJsonValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return kv.getValueAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return kv.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前对象转换为JSON字节数组
|
||||
* 避免Jackson序列化Optional类型的问题
|
||||
*/
|
||||
@JsonIgnore
|
||||
public byte[] toJsonBytes() {
|
||||
try {
|
||||
ObjectNode json = JacksonUtil.newObjectNode();
|
||||
json.put("lastUpdateTs", lastUpdateTs);
|
||||
if (version != null) {
|
||||
json.put("version", version);
|
||||
}
|
||||
|
||||
// 处理KvEntry序列化
|
||||
ObjectNode kvJson = JacksonUtil.newObjectNode();
|
||||
kvJson.put("key", kv.getKey());
|
||||
kvJson.put("dataType", kv.getDataType().name());
|
||||
|
||||
// 根据数据类型序列化值,避免Optional问题
|
||||
switch (kv.getDataType()) {
|
||||
case STRING:
|
||||
kv.getStrValue().ifPresent(value -> kvJson.put("value", value));
|
||||
break;
|
||||
case LONG:
|
||||
kv.getLongValue().ifPresent(value -> kvJson.put("value", value));
|
||||
break;
|
||||
case BOOLEAN:
|
||||
kv.getBooleanValue().ifPresent(value -> kvJson.put("value", value));
|
||||
break;
|
||||
case DOUBLE:
|
||||
kv.getDoubleValue().ifPresent(value -> kvJson.put("value", value));
|
||||
break;
|
||||
case JSON:
|
||||
kv.getJsonValue().ifPresent(value -> kvJson.put("value", value));
|
||||
break;
|
||||
default:
|
||||
// 如果没有匹配的类型,将值作为字符串处理
|
||||
kvJson.put("value", kv.getValueAsString());
|
||||
break;
|
||||
}
|
||||
|
||||
json.set("kv", kvJson);
|
||||
return JacksonUtil.writeValueAsBytes(json);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize BaseAttributeKvEntry to JSON bytes", e);
|
||||
throw new RuntimeException("Failed to serialize BaseAttributeKvEntry", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON字节数组反序列化为BaseAttributeKvEntry对象
|
||||
* 避免Jackson反序列化Optional类型的问题
|
||||
*/
|
||||
public static BaseAttributeKvEntry fromJsonBytes(byte[] jsonBytes) {
|
||||
try {
|
||||
JsonNode json = JacksonUtil.fromBytes(jsonBytes);
|
||||
|
||||
long lastUpdateTs = json.get("lastUpdateTs").asLong();
|
||||
Integer version = json.has("version") ? json.get("version").asInt() : null;
|
||||
|
||||
// 解析KvEntry
|
||||
JsonNode kvJson = json.get("kv");
|
||||
String key = kvJson.get("key").asText();
|
||||
DataType dataType = DataType.valueOf(kvJson.get("dataType").asText());
|
||||
|
||||
KvEntry kvEntry;
|
||||
switch (dataType) {
|
||||
case STRING:
|
||||
String strValue = kvJson.has("value") ? kvJson.get("value").asText() : null;
|
||||
kvEntry = new StringDataEntry(key, strValue);
|
||||
break;
|
||||
case LONG:
|
||||
Long longValue = kvJson.has("value") ? kvJson.get("value").asLong() : null;
|
||||
kvEntry = new LongDataEntry(key, longValue);
|
||||
break;
|
||||
case BOOLEAN:
|
||||
Boolean boolValue = kvJson.has("value") ? kvJson.get("value").asBoolean() : null;
|
||||
kvEntry = new BooleanDataEntry(key, boolValue);
|
||||
break;
|
||||
case DOUBLE:
|
||||
Double doubleValue = kvJson.has("value") ? kvJson.get("value").asDouble() : null;
|
||||
kvEntry = new DoubleDataEntry(key, doubleValue);
|
||||
break;
|
||||
case JSON:
|
||||
String jsonValue = kvJson.has("value") ? kvJson.get("value").asText() : null;
|
||||
kvEntry = new JsonDataEntry(key, jsonValue);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported data type: " + dataType);
|
||||
}
|
||||
|
||||
return new BaseAttributeKvEntry(kvEntry, lastUpdateTs, version);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to deserialize BaseAttributeKvEntry from JSON bytes", e);
|
||||
throw new RuntimeException("Failed to deserialize BaseAttributeKvEntry", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
|
||||
import sanbing.jcpp.infrastructure.util.validation.Length;
|
||||
import sanbing.jcpp.infrastructure.util.validation.NoXss;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class BasicKvEntry implements KvEntry {
|
||||
|
||||
@Length(fieldName = "attribute key")
|
||||
@NoXss
|
||||
private final String key;
|
||||
|
||||
protected BasicKvEntry(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getStrValue() {
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getLongValue() {
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> getBooleanValue() {
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getDoubleValue() {
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getJsonValue() {
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BasicKvEntry that)) return false;
|
||||
return Objects.equals(key, that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BasicKvEntry{" +
|
||||
"key='" + key + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BooleanDataEntry extends BasicKvEntry {
|
||||
private final Boolean value;
|
||||
|
||||
public BooleanDataEntry(String key, Boolean value) {
|
||||
super(key);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType getDataType() {
|
||||
return DataType.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> getBooleanValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BooleanDataEntry that)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
return Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BooleanDataEntry{" +
|
||||
"value=" + value +
|
||||
"} " + super.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return Boolean.toString(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum DataType {
|
||||
|
||||
BOOLEAN(0),
|
||||
LONG(1),
|
||||
DOUBLE(2),
|
||||
STRING(3),
|
||||
JSON(4);
|
||||
|
||||
@Getter
|
||||
private final int protoNumber; // Corresponds to KeyValueType
|
||||
|
||||
DataType(int protoNumber) {
|
||||
this.protoNumber = protoNumber;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DoubleDataEntry extends BasicKvEntry {
|
||||
|
||||
private final Double value;
|
||||
|
||||
public DoubleDataEntry(String key, Double value) {
|
||||
super(key);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType getDataType() {
|
||||
return DataType.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getDoubleValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof DoubleDataEntry that)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
return Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DoubleDataEntry{" +
|
||||
"value=" + value +
|
||||
"} " + super.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return Double.toString(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.data.kv;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class JsonDataEntry extends BasicKvEntry {
|
||||
|
||||
private final String value;
|
||||
|
||||
public JsonDataEntry(String key, String value) {
|
||||
super(key);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType getDataType() {
|
||||
return DataType.JSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getJsonValue() {
|
||||
return Optional.ofNullable(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof JsonDataEntry that)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
return Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JsonDataEntry{" +
|
||||
"value=" + value +
|
||||
"} " + super.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user