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

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sanbing.jcpp.app.dal.mapper.AttributeMapper">
<!-- 查询实体的所有属性 -->
<select id="findByEntity" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr WHERE entity_id = #{entityId}
</select>
<!-- 查询实体的特定属性 -->
<select id="findByEntityAndKey" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr WHERE entity_id = #{entityId} AND attr_key = #{attrKey}
</select>
<!-- 查询实体在指定属性类型下的所有属性 (兼容原JPA方法) -->
<select id="findAllByEntityIdAndAttributeType" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr WHERE entity_id = #{entityId}
</select>
<!-- 根据实体ID和属性键列表查询属性 -->
<select id="findAllByIdAndAttrKey" resultType="sanbing.jcpp.app.dal.entity.Attribute">
SELECT * FROM t_attr
WHERE entity_id = #{entityId}
AND attr_key IN
<foreach collection="attrKeys" item="key" open="(" separator="," close=")">
#{key}
</foreach>
</select>
<!-- 删除指定实体的指定属性 -->
<delete id="deleteByEntityIdAndKey">
DELETE FROM t_attr WHERE entity_id = #{entityId} AND attr_key = #{attrKey}
</delete>
</mapper>

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sanbing.jcpp.app.dal.mapper.GunMapper">
<!-- 充电枪带状态信息的分页查询 -->
<select id="selectGunWithStatusPage" resultType="sanbing.jcpp.app.adapter.response.GunWithStatusResponse">
SELECT
<!-- 充电枪基本信息 -->
g.id,
g.created_time,
g.updated_time,
g.gun_name,
g.gun_no,
g.gun_code,
g.station_id,
g.pile_id,
g.additional_info,
g.version,
<!-- 充电站信息 -->
s.station_name,
<!-- 充电桩信息 -->
p.pile_name,
p.pile_code,
<!-- 状态相关属性 -->
a_run_status.str_v as run_status
FROM t_gun g
<!-- LEFT JOIN 获取充电站信息 -->
LEFT JOIN t_station s ON g.station_id = s.id
<!-- LEFT JOIN 获取充电桩信息 -->
LEFT JOIN t_pile p ON g.pile_id = p.id
<!-- LEFT JOIN 获取充电枪运行状态属性 -->
LEFT JOIN t_attr a_run_status ON (
a_run_status.entity_id = g.id AND
a_run_status.attr_key = 'gunRunStatus'
)
<!-- 动态WHERE条件 -->
<where>
<if test="request.gunName != null and request.gunName != ''">
AND g.gun_name LIKE CONCAT('%', #{request.gunName}, '%')
</if>
<if test="request.gunCode != null and request.gunCode != ''">
AND g.gun_code LIKE CONCAT('%', #{request.gunCode}, '%')
</if>
<if test="request.stationId != null">
AND g.station_id = #{request.stationId}
</if>
<if test="request.pileId != null">
AND g.pile_id = #{request.pileId}
</if>
<if test="request.gunNo != null and request.gunNo != ''">
AND g.gun_no = #{request.gunNo}
</if>
<if test="request.runStatus != null">
AND a_run_status.str_v = #{request.runStatus.code}
</if>
</where>
<!-- 动态ORDER BY -->
ORDER BY
<choose>
<when test="request.sortField == 'gunName'">
g.gun_name ${request.sortOrder}
</when>
<when test="request.sortField == 'gunCode'">
g.gun_code ${request.sortOrder}
</when>
<when test="request.sortField == 'gunNo'">
g.gun_no ${request.sortOrder}
</when>
<when test="request.sortField == 'runStatus'">
a_run_status.str_v ${request.sortOrder}
</when>
<when test="request.sortField == 'createdTime'">
g.created_time ${request.sortOrder}
</when>
<when test="request.sortField == 'updatedTime'">
g.updated_time ${request.sortOrder}
</when>
<otherwise>
g.created_time DESC
</otherwise>
</choose>
</select>
<!-- 根据充电桩编码和充电枪编码查询充电枪 -->
<select id="selectByPileCodeAndGunCode" resultType="sanbing.jcpp.app.dal.entity.Gun">
SELECT g.* FROM t_gun g
INNER JOIN t_pile p ON g.pile_id = p.id
WHERE p.pile_code = #{pileCode} AND g.gun_code = #{gunCode}
</select>
<!-- 统计充电桩下的充电枪数量 -->
<select id="countByPileId" resultType="long">
SELECT COUNT(*) FROM t_gun WHERE pile_id = #{pileId}
</select>
<!-- 统计空闲状态的充电枪数量 (IDLE) -->
<select id="countIdleGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计已插枪未充电状态的充电枪数量 (INSERTED) -->
<select id="countInsertedGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计充电中状态的充电枪数量 (CHARGING) -->
<select id="countChargingGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计充电完成状态的充电枪数量 (CHARGE_COMPLETE) -->
<select id="countChargeCompleteGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计放电准备状态的充电枪数量 (DISCHARGE_READY) -->
<select id="countDischargeReadyGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计放电中状态的充电枪数量 (DISCHARGING) -->
<select id="countDischargingGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计放电完成状态的充电枪数量 (DISCHARGE_COMPLETE) -->
<select id="countDischargeCompleteGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计预约状态的充电枪数量 (RESERVED) -->
<select id="countReservedGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
<!-- 统计故障状态的充电枪数量 (FAULT) -->
<select id="countFaultGuns" resultType="long">
SELECT COUNT(*) FROM t_gun g
LEFT JOIN t_attr a ON (a.entity_id = g.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{status}
</select>
</mapper>

View File

@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sanbing.jcpp.app.dal.mapper.PileMapper">
<!-- 充电桩带状态信息的分页查询 -->
<select id="selectPileWithStatusPage" resultType="sanbing.jcpp.app.adapter.response.PileWithStatusResponse">
SELECT
<!-- 充电桩基本信息 -->
p.id,
p.created_time,
p.updated_time,
p.pile_name,
p.pile_code,
p.protocol,
p.station_id,
p.brand,
p.model,
p.manufacturer,
p.type,
p.additional_info,
p.version,
<!-- 状态相关属性 -->
a_status.str_v as status,
a_connected.last_update_ts as connected_at,
a_disconnected.last_update_ts as disconnected_at,
a_last_active.last_update_ts as last_active_time
FROM t_pile p
<!-- LEFT JOIN 获取充电桩状态属性 -->
LEFT JOIN t_attr a_status ON (
a_status.entity_id = p.id AND
a_status.attr_key = #{statusKey.code}
)
<!-- LEFT JOIN 获取最后连接时间属性 -->
LEFT JOIN t_attr a_connected ON (
a_connected.entity_id = p.id AND
a_connected.attr_key = #{connectedAtKey.code}
)
<!-- LEFT JOIN 获取最后断开时间属性 -->
LEFT JOIN t_attr a_disconnected ON (
a_disconnected.entity_id = p.id AND
a_disconnected.attr_key = #{disconnectedAtKey.code}
)
<!-- LEFT JOIN 获取最后活跃时间属性 -->
LEFT JOIN t_attr a_last_active ON (
a_last_active.entity_id = p.id AND
a_last_active.attr_key = #{lastActiveTimeKey.code}
)
<!-- 动态WHERE条件 -->
<where>
<if test="request.pileName != null and request.pileName != ''">
AND p.pile_name LIKE CONCAT('%', #{request.pileName}, '%')
</if>
<if test="request.pileCode != null and request.pileCode != ''">
AND p.pile_code LIKE CONCAT('%', #{request.pileCode}, '%')
</if>
<if test="request.protocol != null and request.protocol != ''">
AND p.protocol = #{request.protocol}
</if>
<if test="request.stationId != null">
AND p.station_id = #{request.stationId}
</if>
<if test="request.brand != null and request.brand != ''">
AND p.brand LIKE CONCAT('%', #{request.brand}, '%')
</if>
<if test="request.model != null and request.model != ''">
AND p.model LIKE CONCAT('%', #{request.model}, '%')
</if>
<if test="request.manufacturer != null and request.manufacturer != ''">
AND p.manufacturer LIKE CONCAT('%', #{request.manufacturer}, '%')
</if>
<if test="request.type != null">
AND p.type = #{request.type}
</if>
<if test="request.status != null">
AND a_status.str_v = #{request.status.code}
</if>
</where>
<!-- 动态ORDER BY -->
ORDER BY
<choose>
<when test="request.sortField == 'pileName'">
p.pile_name ${request.sortOrder}
</when>
<when test="request.sortField == 'pileCode'">
p.pile_code ${request.sortOrder}
</when>
<when test="request.sortField == 'protocol'">
p.protocol ${request.sortOrder}
</when>
<when test="request.sortField == 'brand'">
p.brand ${request.sortOrder}
</when>
<when test="request.sortField == 'model'">
p.model ${request.sortOrder}
</when>
<when test="request.sortField == 'manufacturer'">
p.manufacturer ${request.sortOrder}
</when>
<when test="request.sortField == 'type'">
p.type ${request.sortOrder}
</when>
<when test="request.sortField == 'status'">
a_status.str_v ${request.sortOrder}
</when>
<when test="request.sortField == 'connectedAt'">
a_connected.last_update_ts ${request.sortOrder}
</when>
<when test="request.sortField == 'disconnectedAt'">
a_disconnected.last_update_ts ${request.sortOrder}
</when>
<when test="request.sortField == 'lastActiveTime'">
a_last_active.last_update_ts ${request.sortOrder}
</when>
<when test="request.sortField == 'createdTime'">
p.created_time ${request.sortOrder}
</when>
<when test="request.sortField == 'updatedTime'">
p.updated_time ${request.sortOrder}
</when>
<otherwise>
p.created_time DESC
</otherwise>
</choose>
</select>
<!-- 根据充电桩编码查询充电桩 -->
<select id="selectByCode" resultType="sanbing.jcpp.app.dal.entity.Pile">
SELECT * FROM t_pile WHERE pile_code = #{pileCode}
</select>
<!-- 统计充电站下的充电桩数量 -->
<select id="countByStationId" resultType="long">
SELECT COUNT(*) FROM t_pile WHERE station_id = #{stationId}
</select>
<!-- 统计在线充电桩数量 -->
<select id="countOnlinePiles" resultType="long">
SELECT COUNT(*) FROM t_pile p
LEFT JOIN t_attr a ON (a.entity_id = p.id AND a.attr_key = #{statusKey})
WHERE a.str_v = #{onlineStatus}
</select>
<!-- 统计离线充电桩数量(包括未设置状态的) -->
<select id="countOfflinePiles" resultType="long">
SELECT COUNT(*) FROM t_pile p
LEFT JOIN t_attr a ON (a.entity_id = p.id AND a.attr_key = #{statusKey})
WHERE a.str_v IS NULL OR a.str_v = #{offlineStatus}
</select>
</mapper>

View File

@@ -0,0 +1,131 @@
/*
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
* 微信mohan_88888
* 抖音:程序员三丙
* 付费课程知识星球https://t.zsxq.com/aKtXo
*/
-- 数据库版本表
CREATE TABLE IF NOT EXISTS t_schema_version
(
version varchar(32) not null primary key
);
-- 插入初始版本
INSERT INTO t_schema_version (version) VALUES ('1.0.0') ON CONFLICT (version) DO NOTHING;
CREATE TABLE IF NOT EXISTS t_user
(
id uuid not null
constraint owner_pkey
primary key,
created_time timestamp default CURRENT_TIMESTAMP not null,
updated_time timestamp,
additional_info jsonb,
status varchar(16) not null,
user_name varchar(255) not null,
user_credentials jsonb not null,
authority varchar(32),
version int default 1
);
CREATE UNIQUE INDEX IF NOT EXISTS uni_user_name
on t_user (user_name);
-- 为t_user表的字段添加注释
COMMENT ON COLUMN t_user.authority IS '用户权限: SYS_ADMIN, TENANT_ADMIN, CUSTOMER_USER, REFRESH_TOKEN, PRE_VERIFICATION_TOKEN';
-- 为authority字段创建索引便于按权限查询用户
CREATE INDEX IF NOT EXISTS idx_user_authority
on t_user (authority);
CREATE TABLE IF NOT EXISTS t_station
(
id uuid not null
constraint station_pkey
primary key,
created_time timestamp default CURRENT_TIMESTAMP not null,
updated_time timestamp,
additional_info jsonb,
station_name varchar(255) not null,
station_code varchar(255) not null,
longitude double precision,
latitude double precision,
province varchar(255),
city varchar(255),
county varchar(255),
address varchar(255),
version int default 1
);
CREATE UNIQUE INDEX IF NOT EXISTS uni_station_code
on t_station (station_code);
CREATE TABLE IF NOT EXISTS t_pile
(
id uuid not null
constraint pile_pkey
primary key,
created_time timestamp default CURRENT_TIMESTAMP not null,
updated_time timestamp,
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,
brand varchar(255),
model varchar(255),
manufacturer varchar(255),
type varchar(16) not null,
version int default 1
);
CREATE UNIQUE INDEX IF NOT EXISTS uni_pile_code
on t_pile (pile_code);
CREATE TABLE IF NOT EXISTS t_gun
(
id uuid not null
primary key,
created_time timestamp default CURRENT_TIMESTAMP not null,
updated_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,
version int default 1
);
CREATE INDEX IF NOT EXISTS idx_gun_pile_id
on t_gun (pile_id);
CREATE INDEX IF NOT EXISTS idx_gun_pile_gun_code
on t_gun (pile_id, gun_code);
CREATE SEQUENCE IF NOT EXISTS attr_kv_version_seq cache 1;
-- 属性表:存储充电桩、充电枪的最新属性数据(如状态等)
-- 采用键值对存储结构设计
CREATE TABLE IF NOT EXISTS t_attr
(
entity_id uuid not null, -- 实体ID (UUID保证全局唯一)
attr_key varchar(255) not null, -- 属性键 (字符串类型提高可读性)
bool_v boolean, -- 布尔值
str_v varchar(10000000), -- 字符串值
long_v bigint, -- 长整型值
dbl_v double precision, -- 双精度值
json_v json, -- JSON值
last_update_ts bigint not null, -- 最后更新时间戳
version int default 0 not null, -- 版本号,用于乐观锁
PRIMARY KEY (entity_id, attr_key)
);

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
开源代码,仅供学习和交流研究使用,商用请联系三丙
微信mohan_88888
抖音:程序员三丙
付费课程知识星球https://t.zsxq.com/aKtXo
-->
<anti-samy-rules>
<directives>
<directive name="omitXmlDeclaration" value="true"/>
<directive name="omitDoctypeDeclaration" value="false"/>
<directive name="maxInputSize" value="100000"/>
<directive name="embedStyleSheets" value="false"/>
<directive name="useXHTML" value="true"/>
<directive name="formatOutput" value="true"/>
</directives>
<common-regexps>
<!--
From W3C:
This attribute assigns a class name or set of class names to an
element. Any number of elements may be assigned the same class
name or names. Multiple class names must be separated by white
space characters.
-->
<regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*"/>
<!-- force non-empty with a '+' at the end instead of '*'
-->
<regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+"/>
<!-- ([\w\\/\.\?=&amp;;\#-~]+|\#(\w)+)
-->
<!-- ([\p{L}/ 0-9&amp;\#-.?=])*
-->
<regexp name="offsiteURL"
value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*"/>
</common-regexps>
<common-attributes>
<attribute name="lang"
description="'lang'属性用于告诉浏览器元素的属性值和内容使用的语言">
<regexp-list>
<regexp value="[a-zA-Z]{2,20}"/>
</regexp-list>
</attribute>
<attribute name="title"
description="'title'属性提供当用户将鼠标悬停在元素上时显示的工具提示文本">
<regexp-list>
<regexp name="htmlTitle"/>
</regexp-list>
</attribute>
<attribute name="href" onInvalid="filterTag">
<regexp-list>
<regexp name="onsiteURL"/>
<regexp name="offsiteURL"/>
</regexp-list>
</attribute>
<attribute name="align"
description="HTML元素的'align'属性是一个方向词,如'left'、'right'或'center'">
<literal-list>
<literal value="center"/>
<literal value="left"/>
<literal value="right"/>
<literal value="justify"/>
<literal value="char"/>
</literal-list>
</attribute>
<attribute name="style"
description="'style'属性使用严格的语法为用户提供更改标签内容的多个属性的能力"/>
</common-attributes>
<global-tag-attributes>
<attribute name="title"/>
<attribute name="lang"/>
<attribute name="style"/>
</global-tag-attributes>
<tags-to-encode>
<tag>g</tag>
<tag>grin</tag>
</tags-to-encode>
<tag-rules>
<tag name="script" action="remove"/>
<tag name="noscript" action="remove"/>
<tag name="iframe" action="remove"/>
<tag name="frameset" action="remove"/>
<tag name="frame" action="remove"/>
<tag name="noframes" action="remove"/>
<tag name="head" action="remove"/>
<tag name="title" action="remove"/>
<tag name="base" action="remove"/>
<tag name="style" action="remove"/>
<tag name="link" action="remove"/>
<tag name="input" action="remove"/>
<tag name="textarea" action="remove"/>
<tag name="br" action="remove"/>
<tag name="p" action="remove"/>
<tag name="div" action="remove"/>
<tag name="span" action="remove"/>
<tag name="i" action="remove"/>
<tag name="b" action="remove"/>
<tag name="strong" action="remove"/>
<tag name="s" action="remove"/>
<tag name="strike" action="remove"/>
<tag name="u" action="remove"/>
<tag name="em" action="remove"/>
<tag name="blockquote" action="remove"/>
<tag name="tt" action="remove"/>
<tag name="a" action="remove"/>
<tag name="ul" action="remove"/>
<tag name="ol" action="remove"/>
<tag name="li" action="remove"/>
<tag name="dl" action="remove"/>
<tag name="dt" action="remove"/>
<tag name="dd" action="remove"/>
</tag-rules>
<css-rules>
<property name="text-decoration" default="none"
description="文本装饰样式">
<category-list>
<category value="visual"/>
</category-list>
<literal-list>
<literal value="underline"/>
<literal value="overline"/>
<literal value="line-through"/>
</literal-list>
</property>
</css-rules>
</anti-samy-rules>