Complete core functions #9

Merged
FatttSnake merged 171 commits from FatttSnake into dev 2024-02-23 11:56:35 +08:00
381 changed files with 22225 additions and 1030 deletions

View File

@@ -10,15 +10,15 @@ java -Djarmode=layertools -jar target/*.jar extract --destination target/extract
if [[ "${JAR_VERSION}" =~ ^.*SNAPSHOT$ ]]
then
docker build -t hub.fatweb.top/fatweb-api:snapshot-latest -t hub.fatweb.top/fatweb-api:$JAR_VERSION -t hub.fatweb.top/fatweb-api:$JAR_VERSION-${BUILD_TIME} .
docker build -t hub.fatweb.top/oxygen-api:snapshot-latest -t hub.fatweb.top/oxygen-api:$JAR_VERSION -t hub.fatweb.top/oxygen-api:$JAR_VERSION-${BUILD_TIME} .
cat "${KEYS_PATH}/docker.password" | docker login hub.fatweb.top -u jenkins --password-stdin
docker push hub.fatweb.top/fatweb-api:snapshot-latest
docker push hub.fatweb.top/fatweb-api:$JAR_VERSION
docker push hub.fatweb.top/fatweb-api:$JAR_VERSION-${BUILD_TIME}
docker push hub.fatweb.top/oxygen-api:snapshot-latest
docker push hub.fatweb.top/oxygen-api:$JAR_VERSION
docker push hub.fatweb.top/oxygen-api:$JAR_VERSION-${BUILD_TIME}
else
docker build -t hub.fatweb.top/fatweb-api:latest -t hub.fatweb.top/fatweb-api:$JAR_VERSION -t hub.fatweb.top/fatweb-api:$JAR_VERSION-${BUILD_TIME} .
docker build -t hub.fatweb.top/oxygen-api:latest -t hub.fatweb.top/oxygen-api:$JAR_VERSION -t hub.fatweb.top/oxygen-api:$JAR_VERSION-${BUILD_TIME} .
cat "${KEYS_PATH}/docker.password" | docker login hub.fatweb.top -u jenkins --password-stdin
docker push hub.fatweb.top/fatweb-api:latest
docker push hub.fatweb.top/fatweb-api:$JAR_VERSION
docker push hub.fatweb.top/fatweb-api:$JAR_VERSION-${BUILD_TIME}
docker push hub.fatweb.top/oxygen-api:latest
docker push hub.fatweb.top/oxygen-api:$JAR_VERSION
docker push hub.fatweb.top/oxygen-api:$JAR_VERSION-${BUILD_TIME}
fi

View File

@@ -1,12 +0,0 @@
drop table if exists t_user;
create table if not exists t_user
(
id bigint not null primary key,
username varchar(20) not null comment '用户名',
password char(70) not null comment '密码',
enable int not null comment '启用',
deleted bigint not null default 0,
version int not null default 0,
constraint t_user_unique unique (username, deleted)
) comment '用户';

982
doc/database.drawio Normal file
View File

@@ -0,0 +1,982 @@
<mxfile host="Electron" modified="2024-01-16T02:51:10.691Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.18 Chrome/120.0.6099.199 Electron/28.1.2 Safari/537.36" etag="flxtUq0V3qvy_peuJ5a6" version="22.1.18" type="device">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="2074" dy="2415" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="SgG1xhS9EfBviSQqJXSq-0" value="t_s_user 系统-用户表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1210" y="-350" width="320" height="480" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-1" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="30" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-2" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-1" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-3" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-1" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-4" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="60" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-5" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-4" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-6" value="username 用户名 - unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-4" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-7" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="90" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-8" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-7" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-9" value="password 密码" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-7" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-10" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="120" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-11" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-10" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-12" value="verify 验证邮箱&amp;nbsp;- unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-10" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-13" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="150" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-14" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-13" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-15" value="forget 忘记密码 - unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-13" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-16" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="180" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-17" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-16" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-18" value="locking 锁定" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-16" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-19" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="210" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-20" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-19" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-21" value="expiration 过期时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-19" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-22" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="240" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-23" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-22" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-24" value="credentials_expiration 认证过期时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-22" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-25" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="270" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-26" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-25" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-27" value="enable 启用" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-25" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-28" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="300" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-29" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-28" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-30" value="current_login_time 当前登录时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-28" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="330" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-32" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-31" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-33" value="current_login_ip 当前登录 IP" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-31" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="360" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-34" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-36" value="last_login_time 上次登录时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-34" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-37" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="390" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-38" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-37" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-39" value="last_login_ip 上次登录 IP" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-37" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-40" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="420" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-41" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-40" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-42" value="create_time 创建时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-40" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-43" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-0" vertex="1">
<mxGeometry y="450" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-44" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-43" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-45" value="update_time 修改时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-43" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-49" value="t_s_power_type 系统-权限类型表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1232.51" y="580" width="275" height="90" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-50" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-49" vertex="1">
<mxGeometry y="30" width="275" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-51" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-50" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-52" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-50" vertex="1">
<mxGeometry x="30" width="245" height="30" as="geometry">
<mxRectangle width="245" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-53" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-49" vertex="1">
<mxGeometry y="60" width="275" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-54" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-53" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-55" value="name 权限类型名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-53" vertex="1">
<mxGeometry x="30" width="245" height="30" as="geometry">
<mxRectangle width="245" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-63" value="t_s_power 系统-权限表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1270" y="430" width="200" height="90" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-64" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-63" vertex="1">
<mxGeometry y="30" width="200" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-65" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-64" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-66" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-64" vertex="1">
<mxGeometry x="30" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-67" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-63" vertex="1">
<mxGeometry y="60" width="200" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-68" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-67" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-69" value="type_id 权限类型" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-67" vertex="1">
<mxGeometry x="30" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-80" value="t_s_menu 系统-菜单表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1350" y="760" width="230" height="180" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-81" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-80" vertex="1">
<mxGeometry y="30" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-82" value="PK,FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-81" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-83" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-81" vertex="1">
<mxGeometry x="70" width="160" height="30" as="geometry">
<mxRectangle width="160" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-84" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-80" vertex="1">
<mxGeometry y="60" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-85" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-84" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-86" value="name 菜单名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-84" vertex="1">
<mxGeometry x="70" width="160" height="30" as="geometry">
<mxRectangle width="160" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-87" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-80" vertex="1">
<mxGeometry y="90" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-88" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-87" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-89" value="url URL" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-87" vertex="1">
<mxGeometry x="70" width="160" height="30" as="geometry">
<mxRectangle width="160" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-90" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-80" vertex="1">
<mxGeometry y="120" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-91" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-90" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-92" value="parent_id 父ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-90" vertex="1">
<mxGeometry x="70" width="160" height="30" as="geometry">
<mxRectangle width="160" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-106" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-80" vertex="1">
<mxGeometry y="150" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-107" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-106" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-108" value="module_id 模块ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-106" vertex="1">
<mxGeometry x="70" width="160" height="30" as="geometry">
<mxRectangle width="160" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-93" value="t_s_func 系统-功能表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1120" y="760" width="210" height="150" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-94" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-93" vertex="1">
<mxGeometry y="30" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-95" value="PK, FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-94" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-96" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-94" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-97" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-93" vertex="1">
<mxGeometry y="60" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-98" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-97" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-99" value="name 功能名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-97" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-100" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-93" vertex="1">
<mxGeometry y="90" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-101" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-100" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-102" value="parent_id 父ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-100" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-103" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-93" vertex="1">
<mxGeometry y="120" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-104" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-103" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-105" value="menu_id 菜单ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-103" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-109" value="t_s_module 系统-模块表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1650" y="760" width="210" height="90" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-110" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-109" vertex="1">
<mxGeometry y="30" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-111" value="PK,FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-110" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-112" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-110" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-113" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-109" vertex="1">
<mxGeometry y="60" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-114" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-113" vertex="1">
<mxGeometry width="70" height="30" as="geometry">
<mxRectangle width="70" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-115" value="name 模块名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-113" vertex="1">
<mxGeometry x="70" width="140" height="30" as="geometry">
<mxRectangle width="140" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-122" value="t_s_operation 系统-操作表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="820" y="760" width="230" height="150" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-123" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-122" vertex="1">
<mxGeometry y="30" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-124" value="PK,FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-123" vertex="1">
<mxGeometry width="60" height="30" as="geometry">
<mxRectangle width="60" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-125" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-123" vertex="1">
<mxGeometry x="60" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-126" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-122" vertex="1">
<mxGeometry y="60" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-127" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-126" vertex="1">
<mxGeometry width="60" height="30" as="geometry">
<mxRectangle width="60" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-128" value="name 操作名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-126" vertex="1">
<mxGeometry x="60" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-129" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-122" vertex="1">
<mxGeometry y="90" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-130" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-129" vertex="1">
<mxGeometry width="60" height="30" as="geometry">
<mxRectangle width="60" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-131" value="code 操作编码" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-129" vertex="1">
<mxGeometry x="60" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-132" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-122" vertex="1">
<mxGeometry y="120" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-133" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-132" vertex="1">
<mxGeometry width="60" height="30" as="geometry">
<mxRectangle width="60" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-134" value="func_id 功能ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-132" vertex="1">
<mxGeometry x="60" width="170" height="30" as="geometry">
<mxRectangle width="170" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-135" value="t_s_group 系统-用户组表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="570" y="160" width="240" height="180" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-136" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-135" vertex="1">
<mxGeometry y="30" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-137" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-136" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-138" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-136" vertex="1">
<mxGeometry x="30" width="210" height="30" as="geometry">
<mxRectangle width="210" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-139" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-135" vertex="1">
<mxGeometry y="60" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-140" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-139" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-141" value="name 用户组名&amp;nbsp;- unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-139" vertex="1">
<mxGeometry x="30" width="210" height="30" as="geometry">
<mxRectangle width="210" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-142" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-135" vertex="1">
<mxGeometry y="90" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-143" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-142" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-144" value="enable 启用" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-142" vertex="1">
<mxGeometry x="30" width="210" height="30" as="geometry">
<mxRectangle width="210" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-145" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-135" vertex="1">
<mxGeometry y="120" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-146" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-145" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-147" value="create_time 创建时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-145" vertex="1">
<mxGeometry x="30" width="210" height="30" as="geometry">
<mxRectangle width="210" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-148" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-135" vertex="1">
<mxGeometry y="150" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-149" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-148" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-150" value="update_time 修改时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-148" vertex="1">
<mxGeometry x="30" width="210" height="30" as="geometry">
<mxRectangle width="210" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-154" value="t_r_user_group 中间表-系统-用户-用户组" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="780" y="-290" width="340" height="120" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-155" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-154" vertex="1">
<mxGeometry y="30" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-156" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-155" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-157" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-155" vertex="1">
<mxGeometry x="30" width="310" height="30" as="geometry">
<mxRectangle width="310" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-158" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-154" vertex="1">
<mxGeometry y="60" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-159" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-158" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-160" value="user_id 用户ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-158" vertex="1">
<mxGeometry x="30" width="310" height="30" as="geometry">
<mxRectangle width="310" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-161" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-154" vertex="1">
<mxGeometry y="90" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-162" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-161" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-163" value="group_id 用户组ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-161" vertex="1">
<mxGeometry x="30" width="310" height="30" as="geometry">
<mxRectangle width="310" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-167" value="t_s_role 系统-角色表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1940" y="160" width="210" height="180" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-168" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-167" vertex="1">
<mxGeometry y="30" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-169" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-168" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-170" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-168" vertex="1">
<mxGeometry x="30" width="180" height="30" as="geometry">
<mxRectangle width="180" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-171" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-167" vertex="1">
<mxGeometry y="60" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-172" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-171" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-173" value="name 角色名&amp;nbsp;- unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-171" vertex="1">
<mxGeometry x="30" width="180" height="30" as="geometry">
<mxRectangle width="180" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-174" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-167" vertex="1">
<mxGeometry y="90" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-175" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-174" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-176" value="enable 启用" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-174" vertex="1">
<mxGeometry x="30" width="180" height="30" as="geometry">
<mxRectangle width="180" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-177" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-167" vertex="1">
<mxGeometry y="120" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-178" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-177" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-179" value="create_time 创建时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-177" vertex="1">
<mxGeometry x="30" width="180" height="30" as="geometry">
<mxRectangle width="180" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-180" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-167" vertex="1">
<mxGeometry y="150" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-181" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-180" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-182" value="update_time 修改时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-180" vertex="1">
<mxGeometry x="30" width="180" height="30" as="geometry">
<mxRectangle width="180" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-183" value="t_r_role_group 中间表-系统-角色-用户组" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1530" y="190" width="330" height="120" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-184" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-183" vertex="1">
<mxGeometry y="30" width="330" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-185" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-184" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-186" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-184" vertex="1">
<mxGeometry x="30" width="300" height="30" as="geometry">
<mxRectangle width="300" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-187" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-183" vertex="1">
<mxGeometry y="60" width="330" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-188" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-187" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-189" value="role_id 角色ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-187" vertex="1">
<mxGeometry x="30" width="300" height="30" as="geometry">
<mxRectangle width="300" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-190" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-183" vertex="1">
<mxGeometry y="90" width="330" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-191" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-190" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-192" value="group_id 用户组ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-190" vertex="1">
<mxGeometry x="30" width="300" height="30" as="geometry">
<mxRectangle width="300" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-196" value="t_r_user_role 中间表-系统-用户-角色" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1610" y="-290" width="300" height="120" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-197" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-196" vertex="1">
<mxGeometry y="30" width="300" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-198" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-197" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-199" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-197" vertex="1">
<mxGeometry x="30" width="270" height="30" as="geometry">
<mxRectangle width="270" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-200" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-196" vertex="1">
<mxGeometry y="60" width="300" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-201" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-200" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-202" value="user_id 用户ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-200" vertex="1">
<mxGeometry x="30" width="270" height="30" as="geometry">
<mxRectangle width="270" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-203" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-196" vertex="1">
<mxGeometry y="90" width="300" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-204" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-203" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-205" value="role_id 角色ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-203" vertex="1">
<mxGeometry x="30" width="270" height="30" as="geometry">
<mxRectangle width="270" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-209" value="t_r_power_role 中间表-系统-权限-角色" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="880" y="190" width="320" height="120" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-210" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-209" vertex="1">
<mxGeometry y="30" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-211" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-210" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-212" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-210" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-213" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-209" vertex="1">
<mxGeometry y="60" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-214" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-213" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-215" value="power_id 权限ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-213" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-216" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-209" vertex="1">
<mxGeometry y="90" width="320" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-217" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-216" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-218" value="role_id 角色ID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-216" vertex="1">
<mxGeometry x="30" width="290" height="30" as="geometry">
<mxRectangle width="290" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-222" value="t_s_user_info 系统-用户资料表" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fontSize=16;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="1238.13" y="-650" width="263.75" height="240" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-223" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="30" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-224" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-223" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-225" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-223" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-226" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="60" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-227" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-226" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-228" value="user_id 用户ID&amp;nbsp;- unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-226" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-229" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="90" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-230" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-229" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-231" value="nickname 昵称" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-229" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-232" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="120" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-233" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-232" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-234" value="avatar 头像" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-232" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-235" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="150" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-236" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-235" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-237" value="email 邮箱 - unique" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-235" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-238" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="180" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-239" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-238" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-240" value="create_time 创建时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-238" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-241" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-222" vertex="1">
<mxGeometry y="210" width="263.75" height="30" as="geometry" />
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-242" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-241" vertex="1">
<mxGeometry width="30" height="30" as="geometry">
<mxRectangle width="30" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-243" value="update_time 修改时间" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;fontSize=16;" parent="SgG1xhS9EfBviSQqJXSq-241" vertex="1">
<mxGeometry x="30" width="233.75" height="30" as="geometry">
<mxRectangle width="233.75" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-263" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-64" target="SgG1xhS9EfBviSQqJXSq-123" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1030" y="470" as="sourcePoint" />
<mxPoint x="1080" y="420" as="targetPoint" />
<Array as="points">
<mxPoint x="1080" y="630" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-265" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-64" target="SgG1xhS9EfBviSQqJXSq-94" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="960" y="490" as="sourcePoint" />
<mxPoint x="1010" y="440" as="targetPoint" />
<Array as="points">
<mxPoint x="1090" y="640" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-266" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-64" target="SgG1xhS9EfBviSQqJXSq-81" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1520" y="490" as="sourcePoint" />
<mxPoint x="1570" y="440" as="targetPoint" />
<Array as="points">
<mxPoint x="1610" y="630" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-267" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-64" target="SgG1xhS9EfBviSQqJXSq-110" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1700" y="520" as="sourcePoint" />
<mxPoint x="1750" y="470" as="targetPoint" />
<Array as="points">
<mxPoint x="1620" y="640" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-268" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-67" target="SgG1xhS9EfBviSQqJXSq-50" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1200" y="590" as="sourcePoint" />
<mxPoint x="1250" y="540" as="targetPoint" />
<Array as="points">
<mxPoint x="1210" y="560" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-269" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-213" target="SgG1xhS9EfBviSQqJXSq-64" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1430" y="180" as="sourcePoint" />
<mxPoint x="1480" y="130" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-271" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-187" target="SgG1xhS9EfBviSQqJXSq-64" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1350" y="320" as="sourcePoint" />
<mxPoint x="1400" y="270" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-273" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-210" target="SgG1xhS9EfBviSQqJXSq-136" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="940" y="470" as="sourcePoint" />
<mxPoint x="990" y="420" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-274" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-190" target="SgG1xhS9EfBviSQqJXSq-168" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1890" y="460" as="sourcePoint" />
<mxPoint x="1940" y="410" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-275" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-161" target="SgG1xhS9EfBviSQqJXSq-136" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1100" y="-560" as="sourcePoint" />
<mxPoint x="920" y="-500" as="targetPoint" />
<Array as="points">
<mxPoint x="520" y="30" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-276" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-203" target="SgG1xhS9EfBviSQqJXSq-168" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="2270" y="80" as="sourcePoint" />
<mxPoint x="2320" y="30" as="targetPoint" />
<Array as="points">
<mxPoint x="2200" y="20" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-277" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-158" target="SgG1xhS9EfBviSQqJXSq-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="830" y="-540" as="sourcePoint" />
<mxPoint x="880" y="-590" as="targetPoint" />
<Array as="points">
<mxPoint x="1160" y="-260" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-278" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-200" target="SgG1xhS9EfBviSQqJXSq-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1770" y="-410" as="sourcePoint" />
<mxPoint x="1820" y="-460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="SgG1xhS9EfBviSQqJXSq-279" value="" style="edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;curved=0;rounded=0;endSize=8;startSize=8;fontSize=12;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="SgG1xhS9EfBviSQqJXSq-226" target="SgG1xhS9EfBviSQqJXSq-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="940" y="-450" as="sourcePoint" />
<mxPoint x="990" y="-500" as="targetPoint" />
<Array as="points">
<mxPoint x="1160" y="-430" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

343
doc/permission.drawio Normal file
View File

@@ -0,0 +1,343 @@
<mxfile host="Electron" modified="2023-12-06T03:01:42.983Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="y2TYcAf6vo8TrTOF2c2W" version="22.1.2" type="device">
<diagram name="第 1 页" id="R2cIEvIs15c5_Cru6pMt">
<mxGraphModel dx="17124" dy="11311" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="SDpw32q9kxCI3RyRmRBe-4" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-1" target="SDpw32q9kxCI3RyRmRBe-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-6" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-1" target="SDpw32q9kxCI3RyRmRBe-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-9" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-11" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-14" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-16" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-18" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-1" value="系统&lt;br style=&quot;font-size: 12px;&quot;&gt;1000000" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;flipH=0;flipV=1;" vertex="1" parent="1">
<mxGeometry x="-15571" y="-8884" width="114" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-23" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-3" target="SDpw32q9kxCI3RyRmRBe-22">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-3" value="系统管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1990000&lt;br&gt;/system" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;" vertex="1" parent="1">
<mxGeometry x="-15344" y="-8884" width="114" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-25" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-24">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-30" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-33" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-32">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-34" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-32">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-35" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-32">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-37" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-7" target="SDpw32q9kxCI3RyRmRBe-36">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-7" value="用户管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1010000&lt;br&gt;/system/user" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;container=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-9774" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-46" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-10" target="SDpw32q9kxCI3RyRmRBe-38">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-47" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-10" target="SDpw32q9kxCI3RyRmRBe-39">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-48" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-10" target="SDpw32q9kxCI3RyRmRBe-40">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-49" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-10" target="SDpw32q9kxCI3RyRmRBe-41">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-10" value="角色管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1020000&lt;br&gt;/system/role" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;container=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-9149.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-50" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-13" target="SDpw32q9kxCI3RyRmRBe-42">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-51" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-13" target="SDpw32q9kxCI3RyRmRBe-43">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-52" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-13" target="SDpw32q9kxCI3RyRmRBe-44">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-53" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-13" target="SDpw32q9kxCI3RyRmRBe-45">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-13" value="用户组管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1030000&lt;br&gt;/system/group" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;container=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-8384" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-57" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-15" target="SDpw32q9kxCI3RyRmRBe-56">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-15" value="日志管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1510000&lt;br&gt;/system/log" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;container=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-7984" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-59" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-17" target="SDpw32q9kxCI3RyRmRBe-58">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-61" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-17" target="SDpw32q9kxCI3RyRmRBe-60">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-17" value="系统设置&lt;br style=&quot;font-size: 12px;&quot;&gt;1520000&lt;br&gt;/system/settings" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;flipV=1;container=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-7755.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-55" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-22" target="SDpw32q9kxCI3RyRmRBe-54">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-22" value="权限管理&lt;br style=&quot;font-size: 12px;&quot;&gt;1040000&lt;br&gt;/system/power" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;flipH=0;" vertex="1" parent="1">
<mxGeometry x="-14329" y="-8073" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-63" value="" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-24" target="SDpw32q9kxCI3RyRmRBe-62">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-78" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-24" target="SDpw32q9kxCI3RyRmRBe-69">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-79" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-24" target="SDpw32q9kxCI3RyRmRBe-71">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-24" value="查询&lt;br style=&quot;font-size: 12px;&quot;&gt;1010100" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9979" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-80" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-29" target="SDpw32q9kxCI3RyRmRBe-72">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-29" value="增加&lt;br style=&quot;font-size: 12px;&quot;&gt;1010200" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9818" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-81" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-32" target="SDpw32q9kxCI3RyRmRBe-73">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-82" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-32" target="SDpw32q9kxCI3RyRmRBe-74">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-32" value="修改&lt;br&gt;1010300" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9706" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-83" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-36" target="SDpw32q9kxCI3RyRmRBe-75">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-84" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-36" target="SDpw32q9kxCI3RyRmRBe-77">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-36" value="删除&lt;br&gt;1010400" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9557" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-94" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-38" target="SDpw32q9kxCI3RyRmRBe-86">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-95" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-38" target="SDpw32q9kxCI3RyRmRBe-87">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-96" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-38" target="SDpw32q9kxCI3RyRmRBe-88">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-38" value="查询&lt;br style=&quot;font-size: 12px;&quot;&gt;1020100" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9366" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-97" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-39" target="SDpw32q9kxCI3RyRmRBe-89">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-39" value="增加&lt;br style=&quot;font-size: 12px;&quot;&gt;1020200" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9184" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-98" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-40" target="SDpw32q9kxCI3RyRmRBe-90">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-99" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-40" target="SDpw32q9kxCI3RyRmRBe-91">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-40" value="修改&lt;br&gt;1020300" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-9052.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-100" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-41" target="SDpw32q9kxCI3RyRmRBe-92">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-102" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-41" target="SDpw32q9kxCI3RyRmRBe-93">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-41" value="删除&lt;br&gt;1020400" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8893.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-111" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-42" target="SDpw32q9kxCI3RyRmRBe-103">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-112" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-42" target="SDpw32q9kxCI3RyRmRBe-104">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-113" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-42" target="SDpw32q9kxCI3RyRmRBe-105">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-42" value="查询&lt;br style=&quot;font-size: 12px;&quot;&gt;1030100" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8663" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-114" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-43" target="SDpw32q9kxCI3RyRmRBe-106">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-43" value="增加&lt;br style=&quot;font-size: 12px;&quot;&gt;1030200" style="whiteSpace=wrap;html=1;fontSize=12;rounded=1;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8487" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-115" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-44" target="SDpw32q9kxCI3RyRmRBe-107">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-116" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-44" target="SDpw32q9kxCI3RyRmRBe-108">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-44" value="修改&lt;br&gt;1030300" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8354" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-117" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-45" target="SDpw32q9kxCI3RyRmRBe-109">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-118" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-45" target="SDpw32q9kxCI3RyRmRBe-110">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-45" value="删除&lt;br&gt;1030400" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8201" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-120" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-54" target="SDpw32q9kxCI3RyRmRBe-119">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-54" value="查询&lt;br&gt;1040100" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-8073" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-123" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-56" target="SDpw32q9kxCI3RyRmRBe-122">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-56" value="查询&lt;br&gt;10510100" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-7984" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-132" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-58" target="SDpw32q9kxCI3RyRmRBe-124">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-133" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-58" target="SDpw32q9kxCI3RyRmRBe-127">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-58" value="查询&lt;br&gt;1520100" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-7840.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-134" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-60" target="SDpw32q9kxCI3RyRmRBe-130">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-136" style="edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;" edge="1" parent="1" source="SDpw32q9kxCI3RyRmRBe-60" target="SDpw32q9kxCI3RyRmRBe-131">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-60" value="修改&lt;br&gt;1520300" style="whiteSpace=wrap;html=1;rounded=1;fontSize=16;" vertex="1" parent="1">
<mxGeometry x="-14020" y="-7670.5" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-62" value="单个&lt;br style=&quot;font-size: 12px;&quot;&gt;1010101&lt;br style=&quot;font-size: 12px;&quot;&gt;system:user:query:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-10055" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-69" value="全部&lt;br&gt;1010102&lt;br&gt;system:user:query:all" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9979" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-70" value="" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13056" y="-8941" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-71" value="列表&lt;br&gt;1010103&lt;br&gt;system:user:query:list" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9903" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-72" value="单个&lt;br&gt;1010201&lt;br&gt;system:user:add:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9818" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-73" value="单个&lt;br&gt;1010301&lt;br&gt;system:user:modify:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9743" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-74" value="密码&lt;br&gt;1010302&lt;br&gt;system:user:modify:password" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9669" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-75" value="单个&lt;br&gt;1010401&lt;br&gt;system:user:delete:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9592" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-77" value="多个&lt;br&gt;1010402&lt;br&gt;system:user:delete:multiple" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9522" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-86" value="单个&lt;br style=&quot;font-size: 12px;&quot;&gt;1020101&lt;br style=&quot;font-size: 12px;&quot;&gt;system:role:query:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9442" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-87" value="全部&lt;br&gt;1020102&lt;br&gt;system:role:query:all" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9366" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-88" value="列表&lt;br&gt;1020103&lt;br&gt;system:role:query:list" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9290" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-89" value="单个&lt;br&gt;1020201&lt;br&gt;system:role:add:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9184" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-90" value="单个&lt;br&gt;1020301&lt;br&gt;system:role:modify:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9090" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-91" value="状态&lt;br&gt;1020302&lt;br&gt;system:role:modify:status" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-9015" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-92" value="单个&lt;br&gt;1020401&lt;br&gt;system:role:delete:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8930" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-93" value="多个&lt;br&gt;1020402&lt;br&gt;system:role:delete:multiple" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8857" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-103" value="单个&lt;br style=&quot;font-size: 12px;&quot;&gt;1030101&lt;br style=&quot;font-size: 12px;&quot;&gt;system:group:query:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8739" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-104" value="全部&lt;br&gt;1030102&lt;br&gt;system:group:query:all" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8663" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-105" value="列表&lt;br&gt;1030103&lt;br&gt;system:group:query:list" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8587" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-106" value="单个&lt;br&gt;1030201&lt;br&gt;system:group:add:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8487" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-107" value="单个&lt;br&gt;1030301&lt;br&gt;system:group:modify:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8391.5" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-108" value="状态&lt;br&gt;1030302&lt;br&gt;system:group:modify:status" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8316.5" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-109" value="单个&lt;br&gt;1030401&lt;br&gt;system:group:delete:one" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8233" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-110" value="多个&lt;br&gt;1030402&lt;br&gt;system:group:delete:multiple" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8160" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-119" value="列表&lt;br&gt;1040103&lt;br&gt;system:power:query:list" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-8073" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-122" value="全部&lt;br&gt;1510101&lt;br&gt;system:log:query:all" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-7984" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-124" value="基础&lt;br&gt;1520101&lt;br&gt;system:settings:query:base" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-7879" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-127" value="邮件&lt;br&gt;1520102&lt;br&gt;system:settings:query:mail" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-7802" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-130" value="基础&lt;br&gt;1520301&lt;br&gt;system:settings:modify:base" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-7709" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="SDpw32q9kxCI3RyRmRBe-131" value="邮件&lt;br&gt;1520302&lt;br&gt;system:settings:modify:mail" style="whiteSpace=wrap;html=1;rounded=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="-13754" y="-7632" width="160" height="60" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

142
pom.xml
View File

@@ -5,14 +5,28 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<version>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.fatweb</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>fatweb-api</name>
<description>fatweb-api</description>
<name>oxygen-api</name>
<description>oxygen-api</description>
<repositories>
<repository>
<id>sonatype-oss-snapshots</id>
<name>sonatype-oss-snapshots</name>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<profiles>
<profile>
@@ -28,7 +42,7 @@
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.3.0</version>
<version>4.4.0</version>
</dependency>
</dependencies>
</profile>
@@ -61,15 +75,12 @@
<properties>
<java.version>17</java.version>
<kotlin.version>1.8.22</kotlin.version>
<kotlin.version>1.9.21</kotlin.version>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
<maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss</maven.build.timestamp.format>
<flyway.version>9.22.3</flyway.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -79,29 +90,27 @@
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -112,51 +121,91 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.3.2</version>
<scope>test</scope>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<!--
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.2</version>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.44.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter-test</artifactId>
<version>3.5.5</version>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.5</version>
</dependency>
-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<groupId>top.fatweb</groupId>
<artifactId>avatar-generator</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.9</version>
</dependency>
</dependencies>
@@ -202,6 +251,19 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
<version>1.9.10</version>
<executions>
<execution>
<phase>pre-site</phase>
<goals>
<goal>dokka</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,36 +0,0 @@
package top.fatweb.api
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.transaction.annotation.EnableTransactionManagement
import java.io.File
import java.util.*
@SpringBootApplication
@EnableTransactionManagement
class FatWebApiApplication
fun main(args: Array<String>) {
val logger = LoggerFactory.getLogger("main")
if (!File("data").isDirectory) {
if (!File("data").mkdir()) {
logger.error("Can not create directory 'data', please try again later.")
return
}
}
if (File("application-config.yml").exists() || File("data/application-config.yml").exists()) {
runApplication<FatWebApiApplication>(*args)
} else {
logger.warn("File application.yml cannot be found in the running path. The configuration file template 'application.example.yml' has been created in directory 'data'. Please change the configuration file content, rename it to 'application.yml', and then restart the server.")
FatWebApiApplication::class.java.getResource("/application-config-template.yml")?.readText()?.let {
File("data/application-config.example.yml").writeText(
it.replace(
"\$uuid\$", UUID.randomUUID().toString().replace("-", "")
)
)
}
}
}

View File

@@ -1,11 +0,0 @@
package top.fatweb.api.annotation
import org.springframework.core.annotation.AliasFor
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ApiVersion(
@get:AliasFor("version") val value: Int = 1,
@get:AliasFor("value") val version: Int = 1
)

View File

@@ -1,25 +0,0 @@
package top.fatweb.api.constant
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit
@Component
@ConfigurationProperties("app.security")
object SecurityConstants {
var headerString = "Authorization"
var tokenPrefix = "Bearer "
var jwtTtl = 2L
var jwtTtlUnit = TimeUnit.HOURS
var jwtKey = "FatWeb"
var jwtIssuer = "FatWeb"
var redisTtl = 20L
var redisTtlUnit = TimeUnit.MINUTES
}

View File

@@ -1,18 +0,0 @@
package top.fatweb.api.constant
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
@Component
@ConfigurationProperties("app")
object ServerConstants {
lateinit var version: String
lateinit var buildTime: String
fun buildZoneDateTime(zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime =
LocalDateTime.parse(buildTime).atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId)
}

View File

@@ -1,17 +0,0 @@
package top.fatweb.api.controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
/**
* <p>
* 用户 前端控制器
* </p>
*
* @author FatttSnake
* @since 2023-10-04
*/
@RestController
@RequestMapping("/api/user")
class UserController

View File

@@ -1,45 +0,0 @@
package top.fatweb.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.*
import top.fatweb.api.annotation.ApiVersion
import top.fatweb.api.converter.UserConverter
import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult
import top.fatweb.api.param.LoginParam
import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.util.WebUtil
@Tag(name = "身份认证", description = "身份认证相关接口")
@ApiVersion(2)
@RestController
class AuthenticationController(val authenticationService: IAuthenticationService, val userConverter: UserConverter) {
@Operation(summary = "登录")
@PostMapping("/login")
fun login(@Valid @RequestBody loginParam: LoginParam) =
ResponseResult.success(
ResponseCode.SYSTEM_LOGIN_SUCCESS,
"Login success",
authenticationService.login(userConverter.loginParamToUser(loginParam))
)
@Operation(summary = "登出")
@PostMapping("/logout")
fun logout(request: HttpServletRequest) =
when (authenticationService.logout(WebUtil.getToken(request))) {
true -> ResponseResult.success(ResponseCode.SYSTEM_LOGOUT_SUCCESS, "Logout success", null)
false -> ResponseResult.fail(ResponseCode.SYSTEM_LOGOUT_FAILED, "Logout failed", null)
}
@Operation(summary = "更新 Token")
@GetMapping("/token")
fun renewToken(request: HttpServletRequest) =
ResponseResult.success(
ResponseCode.SYSTEM_TOKEN_RENEW_SUCCESS,
"Token renew success",
authenticationService.renewToken(WebUtil.getToken(request))
)
}

View File

@@ -1,17 +0,0 @@
package top.fatweb.api.converter
import org.springframework.stereotype.Component
import top.fatweb.api.entity.permission.User
import top.fatweb.api.param.LoginParam
@Component
object UserConverter {
fun loginParamToUser(loginParam: LoginParam): User {
val user = User().apply {
username = loginParam.username
password = loginParam.password
}
return user
}
}

View File

@@ -1,6 +0,0 @@
package top.fatweb.api.entity.common
enum class BusinessCode(val code: Int) {
SYSTEM(100),
DATABASE(200)
}

View File

@@ -1,24 +0,0 @@
package top.fatweb.api.entity.common
enum class ResponseCode(val code: Int) {
SYSTEM_OK(BusinessCode.SYSTEM, 0),
SYSTEM_LOGIN_SUCCESS(BusinessCode.SYSTEM, 20),
SYSTEM_PASSWORD_CHANGE_SUCCESS(BusinessCode.SYSTEM, 21),
SYSTEM_LOGOUT_SUCCESS(BusinessCode.SYSTEM, 22),
SYSTEM_TOKEN_RENEW_SUCCESS(BusinessCode.SYSTEM, 23),
SYSTEM_UNAUTHORIZED(BusinessCode.SYSTEM, 30),
SYSTEM_USERNAME_NOT_FOUND(BusinessCode.SYSTEM, 31),
SYSTEM_ACCESS_DENIED(BusinessCode.SYSTEM, 32),
SYSTEM_USER_DISABLE(BusinessCode.SYSTEM, 33),
SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.SYSTEM, 34),
SYSTEM_OLD_PASSWORD_NOT_MATCH(BusinessCode.SYSTEM, 35),
SYSTEM_LOGOUT_FAILED(BusinessCode.SYSTEM, 36),
SYSTEM_TOKEN_ILLEGAL(BusinessCode.SYSTEM, 37),
SYSTEM_TOKEN_HAS_EXPIRED(BusinessCode.SYSTEM, 38),
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 40),
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 41),
SYSTEM_ERROR(BusinessCode.SYSTEM, 50),
SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51);
constructor(businessCode: BusinessCode, code: Int) : this(businessCode.code * 100 + code)
}

View File

@@ -1,29 +0,0 @@
package top.fatweb.api.entity.common
import io.swagger.v3.oas.annotations.media.Schema
import java.io.Serializable
class ResponseResult<T> private constructor(
@Schema(description = "响应码", defaultValue = "200")
val code: Int,
@Schema(description = "是否调用成功")
val success: Boolean,
@Schema(description = "信息")
val msg: String,
@Schema(description = "数据")
val data: T?
) : Serializable {
companion object {
fun <T> build(code: ResponseCode, success: Boolean, msg: String, data: T?) =
ResponseResult(code.code, success, msg, data)
fun <T> success(code: ResponseCode = ResponseCode.SYSTEM_OK, msg: String = "success", data: T? = null) =
build(code, true, msg, data)
fun <T> fail(code: ResponseCode = ResponseCode.SYSTEM_ERROR, msg: String = "fail", data: T? = null) =
build(code, false, msg, data)
}
}

View File

@@ -1,44 +0,0 @@
package top.fatweb.api.entity.permission
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonTypeInfo
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class LoginUser() : UserDetails {
lateinit var user: User
@JsonIgnore
private var authorities: List<GrantedAuthority>? = null
constructor(user: User) : this() {
this.user = user
}
@JsonIgnore
override fun getAuthorities(): List<GrantedAuthority> {
authorities?.let { return it }
authorities = emptyList()
return authorities as List<GrantedAuthority>
}
@JsonIgnore
override fun getPassword(): String? = user.password
@JsonIgnore
override fun getUsername(): String? = user.username
@JsonIgnore
override fun isAccountNonExpired(): Boolean = true
@JsonIgnore
override fun isAccountNonLocked(): Boolean = true
@JsonIgnore
override fun isCredentialsNonExpired(): Boolean = true
@JsonIgnore
override fun isEnabled(): Boolean = user.enable == 1
}

View File

@@ -1,54 +0,0 @@
package top.fatweb.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
/**
* <p>
* 用户
* </p>
*
* @author FatttSnake
* @since 2023-10-04
*/
@TableName("t_user")
class User() : Serializable {
constructor(username: String, password: String, enable: Boolean = true) : this() {
this.username = username
this.password = password
this.enable = if (enable) 1 else 0
}
@TableId("id")
var id: Long? = null
/**
* 用户名
*/
@TableField("username")
var username: String? = null
/**
* 密码
*/
@TableField("password")
var password: String? = null
/**
* 启用
*/
@TableField("enable")
var enable: Int? = null
@TableField("deleted")
@TableLogic
var deleted: Long? = null
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "User{id=$id, username=$username, password=$password, enable=$enable, deleted=$deleted, version=$version}"
}
}

View File

@@ -1,3 +0,0 @@
package top.fatweb.api.exception
class TokenHasExpiredException : RuntimeException("Token has expired")

View File

@@ -1,46 +0,0 @@
package top.fatweb.api.filter
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import org.springframework.web.filter.OncePerRequestFilter
import top.fatweb.api.constant.SecurityConstants
import top.fatweb.api.entity.permission.LoginUser
import top.fatweb.api.exception.TokenHasExpiredException
import top.fatweb.api.util.JwtUtil
import top.fatweb.api.util.RedisUtil
import top.fatweb.api.util.WebUtil
@Component
class JwtAuthenticationTokenFilter(private val redisUtil: RedisUtil) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val tokenWithPrefix = request.getHeader(SecurityConstants.headerString)
if (!StringUtils.hasText(tokenWithPrefix) || "/error/thrown" == request.servletPath) {
filterChain.doFilter(request, response)
return
}
val token = WebUtil.getToken(tokenWithPrefix)
JwtUtil.parseJwt(token)
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + token
val loginUser = redisUtil.getObject<LoginUser>(redisKey)
loginUser ?: let { throw TokenHasExpiredException() }
redisUtil.setExpire(redisKey, SecurityConstants.redisTtl, SecurityConstants.redisTtlUnit)
val authenticationToken = UsernamePasswordAuthenticationToken(loginUser, null, loginUser.authorities)
SecurityContextHolder.getContext().authentication = authenticationToken
filterChain.doFilter(request, response)
}
}

View File

@@ -1,67 +0,0 @@
package top.fatweb.api.handler
import com.auth0.jwt.exceptions.JWTDecodeException
import com.auth0.jwt.exceptions.SignatureVerificationException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.authentication.InsufficientAuthenticationException
import org.springframework.security.authentication.InternalAuthenticationServiceException
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult
import top.fatweb.api.exception.TokenHasExpiredException
@RestControllerAdvice
class ExceptionHandler {
private val log: Logger = LoggerFactory.getLogger(this::class.java)
@ExceptionHandler(value = [Exception::class])
fun exceptionHandler(e: Exception): ResponseResult<*> {
return when (e) {
is InsufficientAuthenticationException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_UNAUTHORIZED, e.localizedMessage, null)
}
is HttpMessageNotReadableException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_REQUEST_ILLEGAL, e.localizedMessage.split(":")[0], null)
}
is MethodArgumentNotValidException -> {
log.debug(e.localizedMessage, e)
val errorMessage = e.allErrors.map { error -> error.defaultMessage }.joinToString(". ")
ResponseResult.fail(ResponseCode.SYSTEM_ARGUMENT_NOT_VALID, errorMessage, null)
}
is InternalAuthenticationServiceException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_USERNAME_NOT_FOUND, "Username not found", null)
}
is BadCredentialsException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_LOGIN_USERNAME_PASSWORD_ERROR, e.localizedMessage, null)
}
is SignatureVerificationException, is JWTDecodeException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_TOKEN_ILLEGAL, "Token illegal", null)
}
is TokenHasExpiredException -> {
log.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_TOKEN_HAS_EXPIRED, e.localizedMessage, null)
}
else -> {
log.error(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.SYSTEM_ERROR, data = null)
}
}
}
}

View File

@@ -1,16 +0,0 @@
package top.fatweb.api.mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import org.apache.ibatis.annotations.Mapper
import top.fatweb.api.entity.permission.User
/**
* <p>
* 用户 Mapper 接口
* </p>
*
* @author FatttSnake
* @since 2023-10-04
*/
@Mapper
interface UserMapper : BaseMapper<User>

View File

@@ -1,17 +0,0 @@
package top.fatweb.api.param
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import java.io.Serializable
@Schema(description = "登录请求参数")
class LoginParam : Serializable {
@Schema(description = "用户名", example = "test", required = true)
@NotBlank(message = "Username can not be blank")
val username: String? = null
@Schema(description = "密码", example = "test123456", required = true)
@NotBlank(message = "Password can not be blank")
val password: String? = null
}

View File

@@ -1,14 +0,0 @@
package top.fatweb.api.service
import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.api.entity.permission.User
/**
* <p>
* 用户 服务类
* </p>
*
* @author FatttSnake
* @since 2023-10-04
*/
interface IUserService : IService<User>

View File

@@ -1,18 +0,0 @@
package top.fatweb.api.service.impl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import org.springframework.stereotype.Service
import top.fatweb.api.entity.permission.User
import top.fatweb.api.mapper.UserMapper
import top.fatweb.api.service.IUserService
/**
* <p>
* 用户 服务实现类
* </p>
*
* @author FatttSnake
* @since 2023-10-04
*/
@Service
class UserServiceImpl : ServiceImpl<UserMapper, User>(), IUserService

View File

@@ -1,13 +0,0 @@
package top.fatweb.api.service.permission
import top.fatweb.api.entity.permission.User
import top.fatweb.api.vo.LoginVo
import top.fatweb.api.vo.TokenVo
interface IAuthenticationService {
fun login(user: User): LoginVo
fun logout(token: String): Boolean
fun renewToken(token: String): TokenVo
}

View File

@@ -1,65 +0,0 @@
package top.fatweb.api.service.permission.impl
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.stereotype.Service
import top.fatweb.api.constant.SecurityConstants
import top.fatweb.api.entity.permission.LoginUser
import top.fatweb.api.entity.permission.User
import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.util.JwtUtil
import top.fatweb.api.util.RedisUtil
import top.fatweb.api.util.WebUtil
import top.fatweb.api.vo.LoginVo
import top.fatweb.api.vo.TokenVo
@Service
class AuthenticationServiceImpl(
private val authenticationManager: AuthenticationManager,
private val redisUtil: RedisUtil
) : IAuthenticationService {
override fun login(user: User): LoginVo {
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(user.username, user.password)
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
authentication ?: let {
throw RuntimeException("Login failed")
}
val loginUser = authentication.principal as LoginUser
loginUser.user.password = ""
val userId = loginUser.user.id.toString()
val jwt = JwtUtil.createJwt(userId)
jwt ?: let {
throw RuntimeException("Login failed")
}
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt
redisUtil.setObject(redisKey, loginUser, SecurityConstants.redisTtl, SecurityConstants.redisTtlUnit)
return LoginVo(jwt)
}
override fun logout(token: String): Boolean =
redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token)
override fun renewToken(token: String): TokenVo {
val oldRedisKey = "${SecurityConstants.jwtIssuer}_login:" + token
redisUtil.delObject(oldRedisKey)
val jwt = JwtUtil.createJwt(WebUtil.getLoginUserId().toString())
jwt ?: let {
throw RuntimeException("Login failed")
}
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt
redisUtil.setObject(
redisKey,
WebUtil.getLoginUser(),
SecurityConstants.redisTtl,
SecurityConstants.redisTtlUnit
)
return TokenVo(jwt)
}
}

View File

@@ -1,19 +0,0 @@
package top.fatweb.api.service.permission.impl
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Service
import top.fatweb.api.entity.permission.LoginUser
import top.fatweb.api.entity.permission.User
import top.fatweb.api.service.IUserService
@Service
class UserDetailsServiceImpl(val userService: IUserService) : UserDetailsService {
override fun loadUserByUsername(username: String?): UserDetails {
val user = userService.getOne(KtQueryWrapper(User()).eq(User::username, username))
user ?: let { throw Exception("Username not found") }
return LoginUser(user)
}
}

View File

@@ -1,31 +0,0 @@
package top.fatweb.api.util
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.servlet.mvc.condition.RequestCondition
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import top.fatweb.api.annotation.ApiVersion
import java.lang.reflect.Method
class ApiResponseMappingHandlerMapping : RequestMappingHandlerMapping() {
private val versionFlag = "{apiVersion}"
private fun createCondition(clazz: Class<*>): RequestCondition<ApiVersionCondition>? {
val classRequestMapping = clazz.getAnnotation(RequestMapping::class.java)
classRequestMapping ?: let { return null }
val mappingUrlBuilder = StringBuilder()
if (classRequestMapping.value.isNotEmpty()) {
mappingUrlBuilder.append(classRequestMapping.value[0])
}
val mappingUrl = mappingUrlBuilder.toString()
if (!mappingUrl.contains(versionFlag)) {
return null
}
val apiVersion = clazz.getAnnotation(ApiVersion::class.java)
return if (apiVersion == null) ApiVersionCondition(1) else ApiVersionCondition(apiVersion.version)
}
override fun getCustomMethodCondition(method: Method): RequestCondition<*>? = createCondition(method.javaClass)
override fun getCustomTypeCondition(handlerType: Class<*>): RequestCondition<*>? = createCondition(handlerType)
}

View File

@@ -1,183 +0,0 @@
package top.fatweb.api.util
import org.springframework.data.redis.core.BoundSetOperations
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit
@Suppress("UNCHECKED_CAST")
@Component
class RedisUtil(private val redisTemplate: RedisTemplate<String, Any>) {
/**
* 设置有效时间
*
* @param key 缓存的键
* @param timeout 超时时间
* @param timeUnit 时间颗粒度
* @return true=设置成功false=设置失败
*/
fun setExpire(key: String, timeout: Long, timeUnit: TimeUnit = TimeUnit.SECONDS) =
redisTemplate.expire(key, timeout, timeUnit)
/**
* 获取有效时间
*
* @param key 缓存的键
* @return 有效时间
*/
fun getExpire(key: String, timeUnit: TimeUnit = TimeUnit.SECONDS) = redisTemplate.getExpire(key, timeUnit)
/**
* 判断 key 是否存在
*
* @param key 缓存的键
* @return true=存在; false=不存在
*/
fun hasKey(key: String) = redisTemplate.hasKey(key)
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
fun keys(pattern: String): Set<String> = redisTemplate.keys(pattern)
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键
* @param value 缓存的值
*/
fun setObject(key: String, value: Any) = redisTemplate.opsForValue().set(key, value)
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键
* @param value 缓存的值
* @param timeout 超时时间
* @param timeUnit 时间颗粒度
*/
fun setObject(key: String, value: Any, timeout: Long, timeUnit: TimeUnit = TimeUnit.SECONDS) =
redisTemplate.opsForValue().set(key, value, timeout, timeUnit)
/**
* 获得缓存的基本对象
*
* @param key 缓存的键
* @return 缓存的值
*/
fun <T> getObject(key: String) = redisTemplate.opsForValue().get(key) as? T
/**
* 删除单个对象
*
* @param key 缓存的键
* @return true=删除成功false=删除失败
*/
fun delObject(key: String) = redisTemplate.delete(key)
/**
* 删除对象集合
*
* @param collection 键集合
* @return 删除个数
*/
fun delObject(collection: Collection<String>) = redisTemplate.delete(collection)
/**
* 缓存 List 数据
*
* @param key 缓存的键
* @param dataList 缓存的 List 数据
* @return 缓存的个数
*/
fun setList(key: String, dataList: List<Any>) = redisTemplate.opsForList().rightPushAll(key, dataList)
/**
* 获得缓存的 List 数据
*
* @param key 缓存的键
* @return 缓存的键对应的 List 数据
*/
fun <T> getList(key: String): List<T>? = redisTemplate.opsForList().range(key, 0, -1) as? List<T>
/**
* 缓存 Set 数据
*
* @param key 缓存的键
* @param dataSet 缓存的 Set 数据
* @return 缓存数据的对象
*/
fun setSet(key: String, dataSet: Set<Any>): BoundSetOperations<String, Any> {
val boundSetOps: BoundSetOperations<String, Any> = redisTemplate.boundSetOps(key)
for (data in dataSet) {
boundSetOps.add(data)
}
return boundSetOps
}
/**
* 获得缓存的 Set 数据
*
* @param key 缓存的键
* @return 缓存的键对应的 Set 数据
*/
fun <T> getSet(key: String): Set<T>? = redisTemplate.opsForSet().members(key) as? Set<T>
/**
* 缓存 Map 数据
*
* @param key 缓存的键
* @param dataMap 缓存的 Map 数据
*/
fun setMap(key: String, dataMap: Map<String, Any>) = redisTemplate.opsForHash<String, Any>().putAll(key, dataMap)
/**
* 获得缓存的 Map 数据
*
* @param key 缓存的键
* @return 缓存的键对应的 Map 数据
*/
fun <T> getMap(key: String): Map<String, T>? =
redisTemplate.opsForHash<String, Any>().entries(key) as? Map<String, T>
/**
* 往 Hash 中存入数据
*
* @param key Redis 键
* @param hKey Hash 键
* @param value 值
*/
fun setMapValue(key: String, hKey: String, value: Any) =
redisTemplate.opsForHash<String, Any>().put(key, hKey, value)
/**
* 获取 Hash 中的数据
*
* @param key Redis 键
* @param hKey Hash 键
* @return Hash 中的对象
*/
fun <T> getMapValue(key: String, hKey: String) = redisTemplate.opsForHash<String, T>().get(key, hKey)
/**
* 删除 Hash 中的数据
*
* @param key Redis 键
* @param hKey Hash 键
*/
fun delMapValue(key: String, hKey: String) = redisTemplate.opsForHash<String, Any>().delete(key, hKey)
/**
* 获取多个 Hash 中的数据
*
* @param key Redis 键
* @param hKeys Hash 键集合
* @return Hash 对象集合
*/
fun <T> getMultiMapValue(key: String, hKeys: Collection<String>): List<T> =
redisTemplate.opsForHash<String, T>().multiGet(key, hKeys)
}

View File

@@ -1,16 +0,0 @@
package top.fatweb.api.util
import jakarta.servlet.http.HttpServletRequest
import org.springframework.security.core.context.SecurityContextHolder
import top.fatweb.api.constant.SecurityConstants
import top.fatweb.api.entity.permission.LoginUser
object WebUtil {
fun getLoginUser() = SecurityContextHolder.getContext().authentication.principal as LoginUser
fun getLoginUserId() = getLoginUser().user.id
fun getToken(tokenWithPrefix: String) = tokenWithPrefix.removePrefix(SecurityConstants.tokenPrefix)
fun getToken(request: HttpServletRequest) = getToken(request.getHeader(SecurityConstants.headerString))
}

View File

@@ -1,11 +0,0 @@
package top.fatweb.api.vo
import io.swagger.v3.oas.annotations.media.Schema
@Schema(description = "登录返回参数")
data class LoginVo(
@Schema(
description = "Token",
example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkYTllYjFkYmVmZDQ0OWRkOThlOGNjNzZlNzZkMDgyNSIsInN1YiI6IjE3MDk5ODYwNTg2Nzk5NzU5MzgiLCJpc3MiOiJGYXRXZWIiLCJpYXQiOjE2OTY1MjgxMTcsImV4cCI6MTY5NjUzNTMxN30.U2ZsyrGk7NbsP-DJfdz9xgWSfect5r2iKQnlEsscAA8"
) val token: String
)

View File

@@ -0,0 +1,67 @@
package top.fatweb.oxygen.api
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.transaction.annotation.EnableTransactionManagement
import java.io.File
import java.util.*
/**
* Application main class
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@SpringBootApplication
@EnableTransactionManagement
@EnableScheduling
class OxygenApiApplication
/**
* Main function
*
* @param args
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun main(args: Array<String>) {
val logger = LoggerFactory.getLogger("main")
if (!File("data").isDirectory) {
if (!File("data").mkdir()) {
logger.error("Can not create directory 'data', please try again later.")
return
}
}
if (!File("data/db").isDirectory) {
if (!File("data/db").mkdir()) {
logger.error("Can not create directory 'data/db', please try again later.")
return
}
}
if (!File("data/db/sqlite.db").isFile || File("data/db/sqlite.db").inputStream()
.use { it.readNBytes(15).toString(Charsets.UTF_8) != "SQLite format 3" }
) {
logger.warn("The 'data/db/sqlite.db' database is lost or damaged, recreating...")
if (File("data/db/sqlite.db").exists() && !File("data/db/sqlite.db").delete()) {
logger.error("Can not recreate database 'data/db/sqlite.db', please try again later.")
}
}
if (File("application-config.yml").exists() || File("data/application-config.yml").exists()) {
runApplication<OxygenApiApplication>(*args)
} else {
logger.warn("File 'application-config.yml' cannot be found in the running path or the data path. The configuration file template 'application-config.example.yml' has been created in directory 'data'. Please change the configuration file content, move it to the running path, rename it to 'application-config.yml', and then restart the server.")
OxygenApiApplication::class.java.getResource("/application-config-template.yml")?.readText()?.let {
File("data/application-config.example.yml").writeText(
it.replace(
"\$uuid\$", UUID.randomUUID().toString().replace("-", "")
)
)
}
}
}

View File

@@ -0,0 +1,32 @@
package top.fatweb.oxygen.api.annotation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.core.annotation.AliasFor
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
/**
* API controller annotation
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Tag
* @see RequestMapping
* @see RestController
*/
@Tag(name = "")
@RequestMapping
@RestController
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ApiController(
val version: Int = 1,
@get:AliasFor(annotation = RestController::class, attribute = "value") val value: String = "",
@get:AliasFor(annotation = RequestMapping::class, attribute = "path") val path: Array<String> = [""],
@get:AliasFor(annotation = Tag::class, attribute = "name") val name: String,
@get:AliasFor(annotation = Tag::class, attribute = "description") val description: String
)

View File

@@ -0,0 +1,27 @@
package top.fatweb.oxygen.api.annotation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.core.annotation.AliasFor
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
/**
* Base controller annotation
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RequestMapping
* @see RestController
*/
@Tag(name = "")
@RequestMapping
@RestController
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class BaseController(
@get:AliasFor(annotation = RequestMapping::class, attribute = "path") val path: Array<String> = [""],
@get:AliasFor(annotation = Tag::class, attribute = "name") val name: String,
@get:AliasFor(annotation = Tag::class, attribute = "description") val description: String
)

View File

@@ -0,0 +1,15 @@
package top.fatweb.oxygen.api.annotation
import top.fatweb.oxygen.api.entity.system.EventLog
/**
* Event log record annotation
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class EventLogRecord(
val event: EventLog.Event
)

View File

@@ -0,0 +1,24 @@
package top.fatweb.oxygen.api.annotation
import io.swagger.v3.oas.annotations.Hidden
import org.springframework.core.annotation.AliasFor
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
/**
* Hidden controller annotation
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Hidden
* @see RequestMapping
* @see RestController
*/
@Hidden
@RequestMapping
@RestController
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class HiddenController(
@get:AliasFor(annotation = RequestMapping::class, attribute = "path") val path: Array<String> = [""]
)

View File

@@ -0,0 +1,58 @@
package top.fatweb.oxygen.api.aop
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.AfterReturning
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.stereotype.Component
import top.fatweb.oxygen.api.annotation.EventLogRecord
import top.fatweb.oxygen.api.service.system.IEventLogService
import top.fatweb.oxygen.api.util.WebUtil
import top.fatweb.oxygen.api.vo.permission.LoginVo
import top.fatweb.oxygen.api.vo.permission.RegisterVo
/**
* Event log record aspect
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IEventLogService
*/
@Aspect
@Component
class EventLogAspect(
private val eventLogService: IEventLogService
) {
/**
* Event log record pointcut
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Pointcut("@annotation(top.fatweb.oxygen.api.annotation.EventLogRecord)")
fun eventLogPointcut() {
}
/**
* Do after event log record pointcut
*
* @param joinPoint Join point
* @param retValue Return value
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see JoinPoint
*/
@AfterReturning(value = "eventLogPointcut()", returning = "retValue")
fun doAfter(joinPoint: JoinPoint, retValue: Any?) {
val annotation = (joinPoint.signature as MethodSignature).method.getAnnotation(EventLogRecord::class.java)
val userId = WebUtil.getLoginUserId() ?: when (retValue) {
is LoginVo -> retValue.userId!!
is RegisterVo -> retValue.userId!!
else -> -1
}
eventLogService.saveEvent(annotation, userId)
}
}

View File

@@ -0,0 +1,59 @@
package top.fatweb.oxygen.api.config
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.boot.jackson.JsonComponent
import org.springframework.context.annotation.Bean
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
/**
* Date format configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@JsonComponent
class DateFormatConfig {
/**
* The format of the time in response when request APIs
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@set:Value("\${spring.jackson.date-format}")
lateinit var dateFormat: String
/**
* The timezone of the time in response when request APIs
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see TimeZone
*/
@set:Value("\${spring.jackson.time-zone}}")
lateinit var timeZone: TimeZone
@Bean
fun jackson2ObjectMapperBuilder() = Jackson2ObjectMapperBuilderCustomizer {
val tz = timeZone
val df: DateFormat = SimpleDateFormat(dateFormat)
df.timeZone = tz
it.failOnEmptyBeans(false).failOnUnknownProperties(false)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).dateFormat(df)
}
@Bean
fun jackson2ObjectMapperBuilderCustomizer() =
Jackson2ObjectMapperBuilderCustomizer {
it.serializerByType(
LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateFormat))
)
}
}

View File

@@ -1,10 +1,16 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import top.fatweb.api.filter.ExceptionFilter
import top.fatweb.oxygen.api.filter.ExceptionFilter
/**
* Filter configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class FilterConfig {
@Bean

View File

@@ -0,0 +1,43 @@
package top.fatweb.oxygen.api.config
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource
import jakarta.annotation.PostConstruct
import org.flywaydb.core.Flyway
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.DependsOn
import top.fatweb.oxygen.api.properties.FlywayProperties
import javax.sql.DataSource
/**
* Flyway configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@DependsOn("flywayProperties")
@Configuration
class FlywayConfig(
private val dataSource: DataSource
) {
@PostConstruct
fun migrateOrder() {
val ds = dataSource as DynamicRoutingDataSource
ds.dataSources.forEach { (k: String, v: DataSource?) ->
val flyway = Flyway.configure()
.dataSource(v)
.locations(*FlywayProperties.locations.map { "$it/$k" }.toTypedArray())
.baselineOnMigrate(FlywayProperties.baselineOnMigrate)
.table(FlywayProperties.table)
.outOfOrder(FlywayProperties.outOfOrder)
.validateOnMigrate(FlywayProperties.validateOnMigrate)
.encoding(FlywayProperties.encoding)
.sqlMigrationPrefix(FlywayProperties.sqlMigrationPrefix)
.sqlMigrationSeparator(FlywayProperties.sqlMigrationSeparator)
.sqlMigrationSuffixes(*FlywayProperties.sqlMigrationSuffixes.toTypedArray())
.baselineVersion(FlywayProperties.baselineVersion)
.load()
flyway.migrate()
}
}
}

View File

@@ -0,0 +1,69 @@
package top.fatweb.oxygen.api.config
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import jakarta.annotation.PostConstruct
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.DependsOn
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Component
import top.fatweb.avatargenerator.GitHubAvatar
import top.fatweb.oxygen.api.entity.permission.User
import top.fatweb.oxygen.api.entity.permission.UserInfo
import top.fatweb.oxygen.api.properties.AdminProperties
import top.fatweb.oxygen.api.service.permission.IUserInfoService
import top.fatweb.oxygen.api.service.permission.IUserService
import top.fatweb.oxygen.api.util.StrUtil
/**
* Application initialization configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IUserService
* @see IUserInfoService
* @see PasswordEncoder
*/
@DependsOn("adminProperties")
@Component
class InitConfig(
private val userService: IUserService,
private val userInfoService: IUserInfoService,
private val passwordEncoder: PasswordEncoder
) {
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@PostConstruct
fun init() {
if (!userService.exists(KtQueryWrapper(User()).eq(User::id, 0))) {
userInfoService.remove(KtQueryWrapper(UserInfo()).eq(UserInfo::userId, 0))
val rawPassword = AdminProperties.password ?: let {
logger.warn("No default administrator password is set, a randomly generated password will be used")
StrUtil.getRandomPassword(10)
}
val encodedPassword = passwordEncoder.encode(rawPassword)
val user = User().apply {
id = 0
username = AdminProperties.username
password = encodedPassword
locking = 0
enable = 1
}
val userInfo = UserInfo().apply {
userId = 0
nickname = AdminProperties.nickname
avatar =
GitHubAvatar.newAvatarBuilder().build().createAsBase64((Long.MIN_VALUE..Long.MAX_VALUE).random())
email = AdminProperties.email
}
if (userService.save(user) && userInfoService.save(userInfo)) {
logger.warn("First startup, create administrator - username: admin, password: $rawPassword")
logger.warn("This information will only be shown once. Please change your password promptly after logging in.")
}
}
}
}

View File

@@ -0,0 +1,28 @@
package top.fatweb.oxygen.api.config
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import retrofit2.converter.jackson.JacksonConverterFactory
/**
* Jackson configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class JacksonConfig {
@Bean
fun jacksonConverterFactory(): JacksonConverterFactory {
val mapper = JsonMapper.builder()
.findAndAddModules()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build()
return JacksonConverterFactory.create(mapper)
}
}

View File

@@ -1,4 +1,4 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor
@@ -6,6 +6,12 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Mybatis-plus configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class MybatisPlusConfig {
@Bean
@@ -16,5 +22,4 @@ class MybatisPlusConfig {
return mybatisPlusInterceptor
}
}

View File

@@ -1,4 +1,4 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonTypeInfo
@@ -12,6 +12,12 @@ import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer
/**
* Redis configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class RedisConfig {
@Bean

View File

@@ -1,4 +1,4 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@@ -6,23 +6,31 @@ import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.*
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import top.fatweb.api.filter.JwtAuthenticationTokenFilter
import top.fatweb.api.handler.JwtAccessDeniedHandler
import top.fatweb.api.handler.JwtAuthenticationEntryPointHandler
import top.fatweb.oxygen.api.filter.JwtAuthenticationTokenFilter
import top.fatweb.oxygen.api.handler.JwtAccessDeniedHandler
import top.fatweb.oxygen.api.handler.JwtAuthenticationEntryPointHandler
/**
* Spring Security configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see JwtAuthenticationTokenFilter
* @see JwtAuthenticationEntryPointHandler
* @see JwtAccessDeniedHandler
*/
@Configuration
@EnableMethodSecurity
class SecurityConfig(
val jwtAuthenticationTokenFilter: JwtAuthenticationTokenFilter,
val authenticationEntryPointHandler: JwtAuthenticationEntryPointHandler,
val accessDeniedHandler: JwtAccessDeniedHandler
private val jwtAuthenticationTokenFilter: JwtAuthenticationTokenFilter,
private val authenticationEntryPointHandler: JwtAuthenticationEntryPointHandler,
private val accessDeniedHandler: JwtAccessDeniedHandler
) {
@Bean
fun passwordEncoder() = BCryptPasswordEncoder()
@@ -47,43 +55,51 @@ class SecurityConfig(
@Bean
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain = httpSecurity
// Disable CSRF
.csrf { csrfConfigurer: CsrfConfigurer<HttpSecurity> -> csrfConfigurer.disable() }
.csrf {
it.disable()
}
// Do not get SecurityContent by Session
.sessionManagement { sessionManagementConfigurer: SessionManagementConfigurer<HttpSecurity?> ->
sessionManagementConfigurer.sessionCreationPolicy(
.sessionManagement {
it.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
}
.authorizeHttpRequests { authorizeHttpRequests: AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry ->
authorizeHttpRequests
.authorizeHttpRequests {
it
// Allow anonymous access
.requestMatchers(
"/login",
"/error/thrown",
"/doc.html",
"/swagger-ui/**",
"/webjars/**",
"/v3/**",
"/swagger-ui.html",
"/favicon.ico"
"/favicon.ico",
"/login",
"/register",
"/forget",
"/retrieve"
).anonymous()
.requestMatchers("/tool/detail/**", "/tool/store", "/tool/store/*", "/system/user/info/*").permitAll()
// Authentication required
.anyRequest().authenticated()
}
.logout { logoutConfigurer: LogoutConfigurer<HttpSecurity> -> logoutConfigurer.disable() }
.logout {
it.disable()
}
.exceptionHandling { exceptionHandlingConfigurer: ExceptionHandlingConfigurer<HttpSecurity?> ->
exceptionHandlingConfigurer.authenticationEntryPoint(
.exceptionHandling {
it.authenticationEntryPoint(
authenticationEntryPointHandler
)
exceptionHandlingConfigurer.accessDeniedHandler(
it.accessDeniedHandler(
accessDeniedHandler
)
}
.cors { cors: CorsConfigurer<HttpSecurity?> ->
cors.configurationSource(
.cors {
it.configurationSource(
corsConfigurationSource()
)
}

View File

@@ -1,22 +1,27 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Contact
import io.swagger.v3.oas.models.info.Info
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import top.fatweb.api.constant.ServerConstants
import top.fatweb.oxygen.api.properties.ServerProperties
/**
* Swagger API doc configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class SwaggerConfig {
@Bean
fun customOpenAPI(): OpenAPI? {
val contact = Contact().name("FatttSnake").url("https://fatweb.top").email("fatttsnake@fatweb.top")
val contact = Contact().name("FatttSnake").url("https://fatweb.top").email("fatttsnake@gmail.com")
return OpenAPI().info(
Info().title("FatWeb API 文档").description("FatWeb 后端 API 文档,包含各个 Controller 调用信息")
Info().title("Oxygen API 文档").description("Oxygen 后端 API 文档,包含各个 Controller 调用信息")
.contact(contact).version(
ServerConstants.version
ServerProperties.version
)
)
}

View File

@@ -0,0 +1,24 @@
package top.fatweb.oxygen.api.config
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import top.fatweb.oxygen.api.interceptor.SysLogInterceptor
/**
* System log configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SysLogInterceptor
* @see WebMvcConfigurer
*/
@Configuration
class SysLogConfig(
private val sysLogInterceptor: SysLogInterceptor
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(sysLogInterceptor).addPathPatterns("/**")
.excludePathPatterns("/error/thrown", "/webjars/**")
}
}

View File

@@ -0,0 +1,23 @@
package top.fatweb.oxygen.api.config
import org.apache.velocity.app.VelocityEngine
import org.apache.velocity.runtime.RuntimeConstants
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Velocity engine configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Configuration
class VelocityEngineConfig {
@Bean
fun velocityEngine() = VelocityEngine().apply {
setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath")
setProperty("classpath.resource.loader.class", ClasspathResourceLoader::class.java.name)
init()
}
}

View File

@@ -0,0 +1,21 @@
package top.fatweb.oxygen.api.config
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import top.fatweb.oxygen.api.annotation.ApiController
/**
* Web MVC configurer configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see WebMvcRegistrations
*/
@Configuration
class WebMvcConfigurerConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer.addPathPrefix("/api/{API_VERSION}") { it.isAnnotationPresent(ApiController::class.java) }
}
}

View File

@@ -1,10 +1,17 @@
package top.fatweb.api.config
package top.fatweb.oxygen.api.config
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import top.fatweb.api.util.ApiResponseMappingHandlerMapping
import top.fatweb.oxygen.api.util.ApiResponseMappingHandlerMapping
/**
* Web MVC registrations configuration
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see WebMvcRegistrations
*/
@Configuration
class WebMvcRegistrationsConfig : WebMvcRegistrations {
override fun getRequestMappingHandlerMapping(): RequestMappingHandlerMapping = ApiResponseMappingHandlerMapping()

View File

@@ -1,13 +1,16 @@
package top.fatweb.api.controller
package top.fatweb.oxygen.api.controller
import io.swagger.v3.oas.annotations.Hidden
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import top.fatweb.oxygen.api.annotation.HiddenController
@Hidden
@RestController
@RequestMapping("/error")
/**
* Exception controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@HiddenController(["/error"])
class ExceptionController {
@RequestMapping("/thrown")
fun thrown(request: HttpServletRequest) {

View File

@@ -0,0 +1,204 @@
package top.fatweb.oxygen.api.controller.api.v1
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import top.fatweb.oxygen.api.annotation.ApiController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.api.v1.avatar.AvatarBaseParam
import top.fatweb.oxygen.api.param.api.v1.avatar.AvatarGitHubParam
import top.fatweb.oxygen.api.service.api.v1.IAvatarService
import top.fatweb.oxygen.api.vo.api.v1.avatar.AvatarBase64Vo
/**
* Avatar controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IAvatarService
*/
@ApiController(value = "avatarControllerV1", path = ["/avatar"], name = "随机头像 V1", description = "随机头像相关接口")
class AvatarController(
private val avatarService: IAvatarService
) {
/**
* Get random avatar
*
* @param avatarBaseParam Avatar base parameters
* @return Avatar byte array
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ByteArray
*/
@Operation(summary = "获取随机头像")
@GetMapping(produces = [MediaType.IMAGE_PNG_VALUE])
fun getRandom(@Valid avatarBaseParam: AvatarBaseParam?): ByteArray =
avatarService.random(avatarBaseParam)
/**
* Get random avatar as base64
*
* @param avatarBaseParam Avatar base parameters
* @return Response object includes avatar base64 string
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ResponseResult
* @see AvatarBase64Vo
*/
@Operation(summary = "获取随机头像 Base64")
@GetMapping("base64")
fun getRandomBase64(
@Valid avatarBaseParam: AvatarBaseParam?
): ResponseResult<AvatarBase64Vo> =
ResponseResult.success(
ResponseCode.API_AVATAR_SUCCESS, data = avatarService.randomBase64(avatarBaseParam)
)
/**
* Get triangle avatar
*
* @param avatarBaseParam Avatar base parameters
* @return Avatar byte array
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ByteArray
*/
@Operation(summary = "三角形头像")
@GetMapping("/triangle", produces = [MediaType.IMAGE_PNG_VALUE])
fun triangle(@Valid avatarBaseParam: AvatarBaseParam?): ByteArray =
avatarService.triangle(avatarBaseParam)
/**
* Get triangle avatar as base64
*
* @param avatarBaseParam Avatar base parameters
* @return Response object includes avatar base64 string
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ResponseResult
* @see AvatarBase64Vo
*/
@Operation(summary = "三角形头像 Base64")
@GetMapping("/triangle/base64")
fun triangleBase64(
@Valid avatarBaseParam: AvatarBaseParam?
): ResponseResult<AvatarBase64Vo> =
ResponseResult.success(
ResponseCode.API_AVATAR_SUCCESS,
data = avatarService.triangleBase64(avatarBaseParam)
)
/**
* Get square avatar
*
* @param avatarBaseParam Avatar base parameters
* @return Avatar byte array
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ByteArray
*/
@Operation(summary = "正方形头像")
@GetMapping("/square", produces = [MediaType.IMAGE_PNG_VALUE])
fun square(@Valid avatarBaseParam: AvatarBaseParam?): ByteArray =
avatarService.square(avatarBaseParam)
/**
* Get square avatar as base64
*
* @param avatarBaseParam Avatar base parameters
* @return Response object includes avatar base64 string
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ResponseResult
* @see AvatarBase64Vo
*/
@Operation(summary = "正方形头像 Base64")
@GetMapping("/square/base64")
fun squareBase64(
@Valid avatarBaseParam: AvatarBaseParam?
): ResponseResult<AvatarBase64Vo> =
ResponseResult.success(
ResponseCode.API_AVATAR_SUCCESS,
data = avatarService.squareBase64(avatarBaseParam)
)
/**
* Get identicon avatar
*
* @param avatarBaseParam Avatar base parameters
* @return Avatar byte array
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ByteArray
*/
@Operation(summary = "Identicon 头像")
@GetMapping("/identicon", produces = [MediaType.IMAGE_PNG_VALUE])
fun identicon(@Valid avatarBaseParam: AvatarBaseParam?): ByteArray =
avatarService.identicon(avatarBaseParam)
/**
* Get identicon avatar as base64
*
* @param avatarBaseParam Avatar base parameters
* @return Response object includes avatar base64 string
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarBaseParam
* @see ResponseResult
* @see AvatarBase64Vo
*/
@Operation(summary = "Identicon 头像 Base64")
@GetMapping("/identicon/base64")
fun identiconBase64(
@Valid avatarBaseParam: AvatarBaseParam?
): ResponseResult<AvatarBase64Vo> =
ResponseResult.success(
ResponseCode.API_AVATAR_SUCCESS,
data = avatarService.identiconBase64(avatarBaseParam)
)
/**
* Get GitHub avatar
*
* @param avatarGitHubParam Avatar base parameters
* @return Avatar byte array
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarGitHubParam
* @see ByteArray
*/
@Operation(summary = "GitHub 头像")
@GetMapping("/github", produces = [MediaType.IMAGE_PNG_VALUE])
fun github(@Valid avatarGitHubParam: AvatarGitHubParam?): ByteArray =
avatarService.github(avatarGitHubParam)
/**
* Get GitHub avatar as base64
*
* @param avatarGitHubParam Avatar base parameters
* @return Response object includes avatar base64 string
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see AvatarGitHubParam
* @see ResponseResult
* @see AvatarBase64Vo
*/
@Operation(summary = "GitHub 头像 Base64")
@GetMapping("/github/base64")
fun githubBase64(
@Valid avatarGitHubParam: AvatarGitHubParam?
): ResponseResult<AvatarBase64Vo> =
ResponseResult.success(
ResponseCode.API_AVATAR_SUCCESS,
data = avatarService.githubBase64(avatarGitHubParam)
)
}

View File

@@ -0,0 +1,187 @@
package top.fatweb.oxygen.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.permission.*
import top.fatweb.oxygen.api.service.permission.IAuthenticationService
import top.fatweb.oxygen.api.util.WebUtil
import top.fatweb.oxygen.api.vo.permission.LoginVo
import top.fatweb.oxygen.api.vo.permission.RegisterVo
import top.fatweb.oxygen.api.vo.permission.TokenVo
/**
* Authentication controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IAuthenticationService
*/
@BaseController(name = "身份认证", description = "身份认证相关接口")
class AuthenticationController(
private val authenticationService: IAuthenticationService
) {
/**
* Register
*
* @param registerParam Register parameters
* @return Response object includes user ID
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RegisterParam
* @see ResponseResult
* @see RegisterVo
*/
@Operation(summary = "注册")
@PostMapping("/register")
fun register(
request: HttpServletRequest,
@Valid @RequestBody registerParam: RegisterParam
): ResponseResult<RegisterVo> = ResponseResult.success(
ResponseCode.PERMISSION_REGISTER_SUCCESS,
data = authenticationService.register(request, registerParam)
)
/**
* Send verify email
*
* @return Response object includes resend result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "发送验证邮件")
@PostMapping("/resend")
fun resend(): ResponseResult<Nothing> {
authenticationService.resend()
return ResponseResult.success(ResponseCode.PERMISSION_RESEND_SUCCESS)
}
/**
* Verify email
*
* @param verifyParam Verify parameters
* @return Response object includes verify result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see VerifyParam
* @see ResponseResult
*/
@Operation(summary = "验证邮箱")
@PostMapping("/verify")
fun verify(@Valid @RequestBody verifyParam: VerifyParam): ResponseResult<Nothing> {
authenticationService.verify(verifyParam)
return ResponseResult.success(ResponseCode.PERMISSION_VERIFY_SUCCESS)
}
/**
* Forget password
*
* @param request
* @param forgetParam Forget parameters
* @return Response object includes forget result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see HttpServletRequest
* @see ForgetParam
* @see ResponseResult
*/
@Operation(summary = "忘记密码")
@PostMapping("/forget")
fun forget(request: HttpServletRequest, @Valid @RequestBody forgetParam: ForgetParam): ResponseResult<Nothing> {
authenticationService.forget(request, forgetParam)
return ResponseResult.success(ResponseCode.PERMISSION_FORGET_SUCCESS)
}
/**
* Retrieve password
*
* @param request
* @param retrieveParam Retrieve parameters
* @return Response object includes retrieve result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see HttpServletRequest
* @see RetrieveParam
* @see ResponseResult
*/
@Operation(summary = "找回密码")
@PostMapping("/retrieve")
fun retrieve(
request: HttpServletRequest,
@Valid @RequestBody retrieveParam: RetrieveParam
): ResponseResult<Nothing> {
authenticationService.retrieve(request, retrieveParam)
return ResponseResult.success(ResponseCode.PERMISSION_RETRIEVE_SUCCESS)
}
/**
* Login
*
* @param request
* @param loginParam Login parameters
* @return Response object includes login result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see HttpServletRequest
* @see LoginParam
* @see ResponseResult
* @see LoginVo
*/
@Operation(summary = "登录")
@PostMapping("/login")
fun login(request: HttpServletRequest, @Valid @RequestBody loginParam: LoginParam): ResponseResult<LoginVo> =
ResponseResult.success(
ResponseCode.PERMISSION_LOGIN_SUCCESS,
"Login success",
authenticationService.login(request, loginParam)
)
/**
* Logout
*
* @param request
* @return Response object includes logout result
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see HttpServletRequest
* @see ResponseResult
*/
@Operation(summary = "登出")
@PostMapping("/logout")
fun logout(request: HttpServletRequest): ResponseResult<Nothing> =
when (authenticationService.logout(WebUtil.getToken(request))) {
true -> ResponseResult.success(ResponseCode.PERMISSION_LOGOUT_SUCCESS, "Logout success", null)
false -> ResponseResult.fail(ResponseCode.PERMISSION_LOGOUT_FAILED, "Logout failed", null)
}
/**
* Renew token
*
* @param request
* @return Response object includes new token
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see HttpServletRequest
* @see ResponseResult
* @see TokenVo
*/
@Operation(summary = "更新 Token")
@GetMapping("/token")
fun renewToken(request: HttpServletRequest): ResponseResult<TokenVo> = ResponseResult.success(
ResponseCode.PERMISSION_TOKEN_RENEW_SUCCESS,
"Token renew success",
authenticationService.renewToken(WebUtil.getToken(request))
)
}

View File

@@ -0,0 +1,170 @@
package top.fatweb.oxygen.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.permission.group.*
import top.fatweb.oxygen.api.service.permission.IGroupService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.GroupWithRoleVo
import top.fatweb.oxygen.api.vo.permission.base.GroupVo
/**
* Group management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IGroupService
*/
@BaseController(path = ["/system/group"], name = "用户组管理", description = "用户组管理相关接口")
class GroupController(
val groupService: IGroupService
) {
/**
* Get group by ID
*
* @param id Group ID
* @return Response object includes group information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see GroupWithRoleVo
*/
@Operation(summary = "获取单个用户组")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:group:query:one')")
fun getOne(@PathVariable id: Long): ResponseResult<GroupWithRoleVo> =
ResponseResult.databaseSuccess(data = groupService.getOne(id))
/**
* Get group paging information
*
* @param groupGetParam Get group parameters
* @return Response object includes group paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupGetParam
* @see ResponseResult
* @see PageVo
* @see GroupWithRoleVo
*/
@Operation(summary = "获取用户组")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:group:query:all')")
fun get(@Valid groupGetParam: GroupGetParam?): ResponseResult<PageVo<GroupWithRoleVo>> =
ResponseResult.databaseSuccess(
data = groupService.getPage(groupGetParam)
)
/**
* Get group list
*
* @return Response object includes group list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see GroupVo
*/
@Operation(summary = "获取用户组列表")
@GetMapping("/list")
@PreAuthorize("hasAnyAuthority('system:group:query:list', 'system:user:add:one', 'system:user:modify:one')")
fun list(): ResponseResult<List<GroupVo>> =
ResponseResult.databaseSuccess(
data = groupService.getList()
)
/**
* Add group
*
* @param groupAddParam Add group parameters
* @return Response object includes group information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupAddParam
* @see ResponseResult
* @see GroupVo
*/
@Operation(summary = "添加用户组")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:group:add:one')")
fun add(@Valid @RequestBody groupAddParam: GroupAddParam): ResponseResult<GroupVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS, data = groupService.add(groupAddParam)
)
/**
* Update group
*
* @param groupUpdateParam Update group parameters
* @return Response object includes group information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupUpdateParam
* @see ResponseResult
* @see GroupVo
*/
@Operation(summary = "修改用户组")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:group:modify:one')")
fun update(@Valid @RequestBody groupUpdateParam: GroupUpdateParam): ResponseResult<GroupVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS, data = groupService.update(groupUpdateParam)
)
/**
* Update status of group
*
* @param groupUpdateStatusParam Update status of group parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupUpdateStatusParam
* @see ResponseResult
*/
@Operation(summary = "修改用户组状态")
@PatchMapping
@PreAuthorize("hasAnyAuthority('system:group:modify:status')")
fun updateStatus(@Valid @RequestBody groupUpdateStatusParam: GroupUpdateStatusParam): ResponseResult<Nothing> {
groupService.status(groupUpdateStatusParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
}
/**
* Delete group by ID
*
* @param id Group ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除用户组")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:group:delete:one')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> {
groupService.deleteOne(id)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
/**
* Delete group by list
*
* @param groupDeleteParam Delete group parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupDeleteParam
* @see ResponseResult
*/
@Operation(summary = "批量删除用户组")
@DeleteMapping
@PreAuthorize("hasAnyAuthority('system:group:delete:multiple')")
fun deleteList(@Valid @RequestBody groupDeleteParam: GroupDeleteParam): ResponseResult<Nothing> {
groupService.delete(groupDeleteParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -0,0 +1,35 @@
package top.fatweb.oxygen.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.service.permission.IPowerService
import top.fatweb.oxygen.api.vo.permission.PowerSetVo
/**
* Power management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IPowerService
*/
@BaseController(path = ["/system/power"], name = "权限管理", description = "权限管理相关接口")
class PowerController(
private val powerService: IPowerService
) {
/**
* Get power list
*
* @return Response object includes power list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see PowerSetVo
*/
@Operation(summary = "获取权限列表")
@GetMapping("/list")
@PreAuthorize("hasAnyAuthority('system:power:query:list', 'system:role:add:one', 'system:role:modify:one')")
fun getList(): ResponseResult<PowerSetVo> = ResponseResult.databaseSuccess(data = powerService.getList())
}

View File

@@ -0,0 +1,170 @@
package top.fatweb.oxygen.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.permission.role.*
import top.fatweb.oxygen.api.service.permission.IRoleService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.RoleWithPowerVo
import top.fatweb.oxygen.api.vo.permission.base.RoleVo
/**
* Role management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IRoleService
*/
@BaseController(path = ["/system/role"], name = "角色管理", description = "角色管理相关接口")
class RoleController(
private val roleService: IRoleService
) {
/**
* Get role by ID
*
* @param id Role ID
* @return Response object includes role information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see RoleWithPowerVo
*/
@Operation(summary = "获取单个角色")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:role:query:one')")
fun getOne(@PathVariable id: Long): ResponseResult<RoleWithPowerVo> =
ResponseResult.databaseSuccess(data = roleService.getOne(id))
/**
* Get role paging information
*
* @param roleGetParam Get role parameters
* @return Response object includes role paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleGetParam
* @see ResponseResult
* @see RoleWithPowerVo
*/
@Operation(summary = "获取角色")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:role:query:all')")
fun get(roleGetParam: RoleGetParam?): ResponseResult<PageVo<RoleWithPowerVo>> =
ResponseResult.databaseSuccess(
data = roleService.getPage(roleGetParam)
)
/**
* Get role list
*
* @return Response object includes role list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see RoleVo
*/
@Operation(summary = "获取角色列表")
@GetMapping("/list")
@PreAuthorize("hasAnyAuthority('system:role:query:list', 'system:group:add:one', 'system:group:modify:one', 'system:user:add:one', 'system:user:modify:one')")
fun list(): ResponseResult<List<RoleVo>> {
return ResponseResult.databaseSuccess(
data = roleService.getList()
)
}
/**
* Add role
*
* @param roleAddParam Add role parameters
* @return Response object includes role information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleAddParam
* @see ResponseResult
* @see RoleVo
*/
@Operation(summary = "添加角色")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:role:add:one')")
fun add(@Valid @RequestBody roleAddParam: RoleAddParam): ResponseResult<RoleVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS, data = roleService.add(roleAddParam)
)
/**
* Update role
*
* @param roleUpdateParam Update role parameters
* @return Response object includes role information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleUpdateParam
* @see ResponseResult
* @see RoleVo
*/
@Operation(summary = "修改角色")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:role:modify:one')")
fun update(@Valid @RequestBody roleUpdateParam: RoleUpdateParam): ResponseResult<RoleVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS, data = roleService.update(roleUpdateParam)
)
/**
* Update status of role
*
* @param roleUpdateStatusParam Update status of role parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleUpdateStatusParam
* @see ResponseResult
*/
@Operation(summary = "修改角色状态")
@PatchMapping
@PreAuthorize("hasAnyAuthority('system:role:modify:status')")
fun status(@Valid @RequestBody roleUpdateStatusParam: RoleUpdateStatusParam): ResponseResult<Nothing> {
roleService.status(roleUpdateStatusParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
}
/**
* Delete role by ID
*
* @param id
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除角色")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:role:delete:one')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> {
roleService.deleteOne(id)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
/**
* Delete role by list
*
* @param roleDeleteParam Delete role parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleDeleteParam
* @see ResponseResult
*/
@Operation(summary = "批量删除角色")
@DeleteMapping
@PreAuthorize("hasAnyAuthority('system:role:delete:multiple')")
fun deleteList(@Valid @RequestBody roleDeleteParam: RoleDeleteParam): ResponseResult<Nothing> {
roleService.delete(roleDeleteParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -0,0 +1,217 @@
package top.fatweb.oxygen.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.permission.user.*
import top.fatweb.oxygen.api.service.permission.IUserService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.UserWithInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithPowerInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithRoleInfoVo
/**
* User management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IUserService
*/
@BaseController(path = ["/system/user"], name = "用户管理", description = "用户管理相关接口")
class UserController(
private val userService: IUserService
) {
/**
* Get current user information
*
* @return Response object includes user information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see UserWithPowerInfoVo
*/
@Operation(summary = "获取当前用户信息")
@GetMapping("/info")
fun getInfo(): ResponseResult<UserWithPowerInfoVo> =
ResponseResult.databaseSuccess(data = userService.getInfo())
/**
* Get basic user information
*
* @param username Username
* @return Response object includes user basic information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see UserWithPowerInfoVo
*/
@Operation(summary = "获取指定用户基本信息")
@GetMapping("/info/{username}")
fun getBasicInfo(@PathVariable username: String): ResponseResult<UserWithInfoVo> =
ResponseResult.databaseSuccess(data = userService.getBasicInfo(username))
/**
* Update current user information
*
* @param userInfoUpdateParam Update user information parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserInfoUpdateParam
* @see ResponseResult
*/
@Operation(summary = "更新当前用户信息")
@PatchMapping("info")
fun updateInfo(@RequestBody @Valid userInfoUpdateParam: UserInfoUpdateParam): ResponseResult<Nothing> =
if (userService.updateInfo(userInfoUpdateParam)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
else ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_FAILED)
/**
* Change password
*
* @param userChangePasswordParam User change password parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserChangePasswordParam
* @see ResponseResult
*/
@Operation(summary = "更改密码")
@PostMapping("info")
fun password(@RequestBody @Valid userChangePasswordParam: UserChangePasswordParam): ResponseResult<Nothing> {
userService.password(userChangePasswordParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
}
/**
* Get user by ID
*
* @param id User ID
* @return Response object includes user information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see UserWithRoleInfoVo
*/
@Operation(summary = "获取单个用户")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:user:query:one')")
fun getOne(@PathVariable id: Long): ResponseResult<UserWithRoleInfoVo> =
ResponseResult.databaseSuccess(data = userService.getOne(id))
/**
* Get user paging information
*
* @param userGetParam Get user parameters
* @return Response object includes user paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserGetParam
* @see ResponseResult
* @see UserWithRoleInfoVo
*/
@Operation(summary = "获取用户")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:user:query:all')")
fun get(@Valid userGetParam: UserGetParam?): ResponseResult<PageVo<UserWithRoleInfoVo>> =
ResponseResult.databaseSuccess(
data = userService.getPage(userGetParam)
)
/**
* Add user
*
* @param userAddParam Add user parameters
* @return Response object includes user information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserAddParam
* @see ResponseResult
* @see UserWithPasswordRoleInfoVo
*/
@Operation(summary = "添加用户")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:user:add:one')")
fun add(@Valid @RequestBody userAddParam: UserAddParam): ResponseResult<UserWithPasswordRoleInfoVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS, data = userService.add(userAddParam)
)
/**
* Update user
*
* @param userUpdateParam Update user parameters
* @return Response object includes user information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserUpdateParam
* @see ResponseResult
* @see UserWithRoleInfoVo
*/
@Operation(summary = "修改用户")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:user:modify:one')")
fun update(@Valid @RequestBody userUpdateParam: UserUpdateParam): ResponseResult<UserWithRoleInfoVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS, data = userService.update(userUpdateParam)
)
/**
* Update user password
*
* @param userUpdatePasswordParam Update user password parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserUpdatePasswordParam
* @see ResponseResult
*/
@Operation(summary = "修改密码")
@PatchMapping
@PreAuthorize("hasAnyAuthority('system:user:modify:password')")
fun password(@Valid @RequestBody userUpdatePasswordParam: UserUpdatePasswordParam): ResponseResult<Nothing> {
userService.password(userUpdatePasswordParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
}
/**
* Delete user by ID
*
* @param id User ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除用户")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:user:delete:one')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> {
userService.deleteOne(id)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
/**
* Delete user by list
*
* @param userDeleteParam Delete user parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserDeleteParam
* @see ResponseResult
*/
@Operation(summary = "批量删除用户")
@DeleteMapping
@PreAuthorize("hasAnyAuthority('system:user:delete:multiple')")
fun deleteList(@Valid @RequestBody userDeleteParam: UserDeleteParam): ResponseResult<Nothing> {
userService.delete(userDeleteParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -0,0 +1,184 @@
package top.fatweb.oxygen.api.controller.system
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.system.*
import top.fatweb.oxygen.api.service.system.ISensitiveWordService
import top.fatweb.oxygen.api.service.system.ISettingsService
import top.fatweb.oxygen.api.vo.system.BaseSettingsVo
import top.fatweb.oxygen.api.vo.system.MailSettingsVo
import top.fatweb.oxygen.api.vo.system.SensitiveWordVo
/**
* System settings management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ISettingsService
* @see ISensitiveWordService
*/
@BaseController(path = ["/system/settings"], name = "系统设置", description = "系统设置相关接口")
class SettingsController(
private val settingsService: ISettingsService,
private val sensitiveWordService: ISensitiveWordService
) {
/**
* Get base settings
*
* @return Response object includes base settings information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see BaseSettingsVo
*/
@Operation(summary = "获取基础设置")
@GetMapping("/base")
@PreAuthorize("hasAnyAuthority('system:settings:query:base')")
fun getApp(): ResponseResult<BaseSettingsVo> = ResponseResult.success(data = settingsService.getBase())
/**
* Update base settings
*
* @param baseSettingsParam Base settings parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see BaseSettingsParam
* @see ResponseResult
*/
@Operation(summary = "更新基础设置")
@PutMapping("/base")
@PreAuthorize("hasAnyAuthority('system:settings:modify:base')")
fun updateApp(@RequestBody baseSettingsParam: BaseSettingsParam): ResponseResult<Nothing> {
settingsService.updateBase(baseSettingsParam)
return ResponseResult.success()
}
/**
* Get mail settings
*
* @return Response object includes mail settings
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see MailSettingsVo
*/
@Operation(summary = "获取邮件设置")
@GetMapping("/mail")
@PreAuthorize("hasAnyAuthority('system:settings:query:mail')")
fun getMail(): ResponseResult<MailSettingsVo> = ResponseResult.success(data = settingsService.getMail())
/**
* Update mail settings
*
* @param mailSettingsParam Mail settings parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see MailSettingsParam
* @see ResponseResult
*/
@Operation(summary = "更新邮件设置")
@PutMapping("/mail")
@PreAuthorize("hasAnyAuthority('system:settings:modify:mail')")
fun updateMail(@RequestBody mailSettingsParam: MailSettingsParam): ResponseResult<Nothing> {
settingsService.updateMail(mailSettingsParam)
return ResponseResult.success()
}
/**
* Send mail test
*
* @param mailSendParam Mail send parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see MailSendParam
* @see ResponseResult
*/
@Operation(summary = "邮件发送测试")
@PostMapping("/mail")
@PreAuthorize("hasAnyAuthority('system:settings:modify:mail')")
fun sendMail(@RequestBody @Valid mailSendParam: MailSendParam): ResponseResult<Nothing> {
settingsService.sendMail(mailSendParam)
return ResponseResult.success()
}
/**
* Get sensitive word settings
*
* @return Response object includes sensitive word settings information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see SensitiveWordVo
*/
@Operation(summary = "获取敏感词配置")
@GetMapping("/sensitive")
@PreAuthorize("hasAnyAuthority('system:settings:query:sensitive')")
fun getSensitive(): ResponseResult<List<SensitiveWordVo>> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_SELECT_SUCCESS, data = sensitiveWordService.get())
/**
* Add sensitive word
*
* @param sensitiveWordAddParam Add sensitive word settings parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SensitiveWordAddParam
* @see ResponseResult
*/
@Operation(summary = "添加敏感词")
@PostMapping("/sensitive")
@PreAuthorize("hasAnyAuthority('system:settings:modify:sensitive')")
fun addSensitive(@RequestBody @Valid sensitiveWordAddParam: SensitiveWordAddParam): ResponseResult<Nothing> {
sensitiveWordService.add(sensitiveWordAddParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_INSERT_SUCCESS)
}
/**
* Update sensitive word
*
* @param sensitiveWordUpdateParam Update sensitive word settings parameters
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SensitiveWordUpdateParam
* @see ResponseResult
*/
@Operation(summary = "修改敏感词")
@PutMapping("/sensitive")
@PreAuthorize("hasAnyAuthority('system:settings:modify:sensitive')")
fun updateSensitive(@RequestBody sensitiveWordUpdateParam: SensitiveWordUpdateParam): ResponseResult<Nothing> {
sensitiveWordService.update(sensitiveWordUpdateParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS)
}
/**
* Delete sensitive word
*
* @see id Sensitive word ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除敏感词")
@DeleteMapping("/sensitive/{id}")
@PreAuthorize("hasAnyAuthority('system:settings:modify:sensitive')")
fun deleteSensitive(@PathVariable id: Long): ResponseResult<Nothing> {
sensitiveWordService.delete(id)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -0,0 +1,113 @@
package top.fatweb.oxygen.api.controller.system
import io.swagger.v3.oas.annotations.Operation
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.system.ActiveInfoGetParam
import top.fatweb.oxygen.api.param.system.OnlineInfoGetParam
import top.fatweb.oxygen.api.service.system.IStatisticsService
import top.fatweb.oxygen.api.vo.system.*
/**
* Statistics management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IStatisticsService
*/
@BaseController(path = ["/system/statistics"], name = "统计接口", description = "系统信息统计相关接口")
class StatisticsController(
private val statisticService: IStatisticsService
) {
/**
* Get software information
*
* @return Response object includes software information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see SoftwareInfoVo
*/
@Operation(summary = "获取软件信息")
@GetMapping("/software")
@PreAuthorize("hasAnyAuthority('system:statistics:query:base')")
fun software(): ResponseResult<SoftwareInfoVo> = ResponseResult.success(data = statisticService.software())
/**
* Get hardware information
*
* @return Response object includes hardware information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see HardwareInfoVo
*/
@Operation(summary = "获取硬件信息")
@GetMapping("/hardware")
@PreAuthorize("hasAnyAuthority('system:statistics:query:base')")
fun hardware(): ResponseResult<HardwareInfoVo> = ResponseResult.success(data = statisticService.hardware())
/**
* Get CPU information
*
* @return Response object includes CPU information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see CpuInfoVo
*/
@Operation(summary = "获取 CPU 信息")
@GetMapping("/cpu")
@PreAuthorize("hasAnyAuthority('system:statistics:query:real')")
fun cpu(): ResponseResult<CpuInfoVo> = ResponseResult.success(data = statisticService.cpu())
/**
* Get storage information
*
* @return Response object includes storage information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see StorageInfoVo
*/
@Operation(summary = "获取存储信息")
@GetMapping("/storage")
@PreAuthorize("hasAnyAuthority('system:statistics:query:real')")
fun storage(): ResponseResult<StorageInfoVo> = ResponseResult.success(data = statisticService.storage())
/**
* Get the history of online users information
*
* @param onlineInfoGetParam Get online information parameters
* @return Response object includes online user information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see OnlineInfoGetParam
* @see ResponseResult
* @see OnlineInfoVo
*/
@Operation(summary = "获取在线用户数量信息")
@GetMapping("/online")
@PreAuthorize("hasAnyAuthority('system:statistics:query:usage')")
fun online(onlineInfoGetParam: OnlineInfoGetParam?): ResponseResult<OnlineInfoVo> =
ResponseResult.success(data = statisticService.online(onlineInfoGetParam))
/**
* Get the history of active information
*
* @param activeInfoGetParam Get active information parameters
* @return Response object includes history of active information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ActiveInfoGetParam
* @see ResponseResult
* @see ActiveInfoVo
*/
@Operation(summary = "获取用户活跃信息")
@GetMapping("/active")
@PreAuthorize("hasAnyAuthority('system:statistics:query:usage')")
fun active(activeInfoGetParam: ActiveInfoGetParam): ResponseResult<ActiveInfoVo> =
ResponseResult.success(data = statisticService.active(activeInfoGetParam))
}

View File

@@ -0,0 +1,45 @@
package top.fatweb.oxygen.api.controller.system
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.system.SysLogGetParam
import top.fatweb.oxygen.api.service.system.ISysLogService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.system.SysLogVo
/**
* System log viewer controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ISysLogService
*/
@BaseController(path = ["/system/log"], name = "系统日志", description = "系统日志相关接口")
class SysLogController(
private val sysLogService: ISysLogService
) {
/**
* Get system log in page
*
* @param sysLogGetParam Get system log parameters
* @return Response object includes system log in page
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SysLogGetParam
* @see ResponseResult
* @see SysLogVo
*/
@Operation(summary = "获取")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:log:query:all')")
fun get(@Valid sysLogGetParam: SysLogGetParam?): ResponseResult<PageVo<SysLogVo>> {
return ResponseResult.success(
ResponseCode.DATABASE_SELECT_SUCCESS, data = sysLogService.getPage(sysLogGetParam)
)
}
}

View File

@@ -0,0 +1,112 @@
package top.fatweb.oxygen.api.controller.tool
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.tool.ToolBaseAddParam
import top.fatweb.oxygen.api.param.tool.ToolBaseUpdateParam
import top.fatweb.oxygen.api.service.tool.IToolBaseService
import top.fatweb.oxygen.api.vo.tool.ToolBaseVo
/**
* Tool base management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IToolBaseService
*/
@BaseController(path = ["/system/tool/base"], name = "工具基板管理", description = "工具基板管理相关接口")
class BaseController(
private val toolBaseService: IToolBaseService
) {
/**
* Get tool base by ID
*
* @param id Tool base ID
* @return Response object includes tool base information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolBaseVo
*/
@Operation(summary = "获取单个基板")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:query:base')")
fun getOne(@PathVariable id: Long): ResponseResult<ToolBaseVo> =
ResponseResult.databaseSuccess(data = toolBaseService.getOne(id))
/**
* Get tool base list
*
* @return Response object includes tool base list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolBaseVo
*/
@Operation(summary = "获取基板")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:tool:query:base', 'system:tool:add:template', 'system:tool:modify:template')")
fun get(): ResponseResult<List<ToolBaseVo>> =
ResponseResult.databaseSuccess(data = toolBaseService.get())
/**
* Add tool base
*
* @param toolBaseAddParam Add tool base parameters
* @return Response object includes tool base information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBaseAddParam
* @see ResponseResult
* @see ToolBaseVo
*/
@Operation(summary = "新增基板")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:tool:add:base')")
fun add(@RequestBody @Valid toolBaseAddParam: ToolBaseAddParam): ResponseResult<ToolBaseVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS,
data = toolBaseService.add(toolBaseAddParam)
)
/**
* Update tool base
*
* @param toolBaseUpdateParam Update tool base parameters
* @return Response object includes tool base information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBaseUpdateParam
* @see ResponseResult
* @see ToolBaseVo
*/
@Operation(summary = "更新基板")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:tool:modify:base')")
fun update(@RequestBody @Valid toolBaseUpdateParam: ToolBaseUpdateParam): ResponseResult<ToolBaseVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS,
data = toolBaseService.update(toolBaseUpdateParam)
)
/**
* Delete tool base by ID
*
* @param id Tool base ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除基板")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:delete:base')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (toolBaseService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
}

View File

@@ -0,0 +1,112 @@
package top.fatweb.oxygen.api.controller.tool
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.tool.ToolCategoryAddParam
import top.fatweb.oxygen.api.param.tool.ToolCategoryUpdateParam
import top.fatweb.oxygen.api.service.tool.IToolCategoryService
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
/**
* Tool category management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IToolCategoryService
*/
@BaseController(path = ["/system/tool/category"], name = "工具类别管理", description = "工具列别管理相关接口")
class CategoryController(
private val toolCategoryService: IToolCategoryService
) {
/**
* Get tool category by ID
*
* @param id Tool category ID
* @return Response object includes tool template information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolCategoryVo
*/
@Operation(summary = "获取单个类别")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:query:category')")
fun getOne(@PathVariable id: Long): ResponseResult<ToolCategoryVo> =
ResponseResult.databaseSuccess(data = toolCategoryService.getOne(id))
/**
* Get tool category list
*
* @return Response object includes tool template list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolCategoryVo
*/
@Operation(summary = "获取类别")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:tool:query:category')")
fun get(): ResponseResult<List<ToolCategoryVo>> =
ResponseResult.databaseSuccess(data = toolCategoryService.get())
/**
* Add tool category
*
* @param toolCategoryAddParam Add tool category parameters
* @return Response object includes tool category information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCategoryAddParam
* @see ResponseResult
* @see ToolCategoryVo
*/
@Operation(summary = "新增类别")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:tool:add:category')")
fun add(@RequestBody @Valid toolCategoryAddParam: ToolCategoryAddParam): ResponseResult<ToolCategoryVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS,
data = toolCategoryService.add(toolCategoryAddParam)
)
/**
* Update tool category
*
* @param toolCategoryUpdateParam Update tool category parameters
* @return Response object includes tool category information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCategoryUpdateParam
* @see ResponseResult
* @see ToolCategoryVo
*/
@Operation(summary = "更新类别")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:tool:modify:category')")
fun update(@RequestBody @Valid toolCategoryUpdateParam: ToolCategoryUpdateParam): ResponseResult<ToolCategoryVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS,
data = toolCategoryService.update(toolCategoryUpdateParam)
)
/**
* Delete tool category
*
* @param id Tool category ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除类别")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:delete:category')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (toolCategoryService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
}

View File

@@ -0,0 +1,204 @@
package top.fatweb.oxygen.api.controller.tool
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.tool.ToolCreateParam
import top.fatweb.oxygen.api.param.tool.ToolUpdateParam
import top.fatweb.oxygen.api.param.tool.ToolUpgradeParam
import top.fatweb.oxygen.api.service.tool.IEditService
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
import top.fatweb.oxygen.api.vo.tool.ToolTemplateVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
/**
* Tool edit controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IEditService
*/
@BaseController(path = ["/tool"], name = "工具编辑", description = "工具编辑相关接口")
class EditController(
private val editService: IEditService
) {
/**
* Get tool template list
*
* @return Response object includes tool template list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "获取模板")
@GetMapping("/template")
fun getTemplate(): ResponseResult<List<ToolTemplateVo>> =
ResponseResult.databaseSuccess(data = editService.getTemplate())
/**
* Get tool template by ID
*
* @param id ID
* @return Response object includes tool template information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "获取单个模板")
@GetMapping("/template/{id}")
fun getTemplate(@PathVariable id: Long): ResponseResult<ToolTemplateVo> =
ResponseResult.databaseSuccess(data = editService.getTemplate(id))
/**
* Get tool category list
*
* @return Response object includes tool category list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolCategoryVo
*/
@Operation(summary = "获取类别")
@GetMapping("/category")
fun getCategory(): ResponseResult<List<ToolCategoryVo>> =
ResponseResult.databaseSuccess(data = editService.getCategory())
/**
* Create tool
*
* @param toolCreateParam Create tool parameters
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCreateParam
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "创建工具")
@PostMapping
fun create(@RequestBody @Valid toolCreateParam: ToolCreateParam): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_INSERT_SUCCESS, data = editService.create(toolCreateParam))
/**
* Upgrade tool
*
* @param toolUpgradeParam Upgrade tool parameters
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolUpgradeParam
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "升级工具")
@PatchMapping
fun upgrade(@RequestBody @Valid toolUpgradeParam: ToolUpgradeParam): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS,
data = editService.upgrade(toolUpgradeParam)
)
/**
* Get personal tool
*
* @return Response object includes tool list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "获取个人工具")
@GetMapping
fun get(): ResponseResult<List<ToolVo>> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_SELECT_SUCCESS, data = editService.get())
/**
* Get tool detail
*
* @param username Username
* @param toolId Tool ID
* @param ver Version
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "获取工具内容")
@GetMapping("/detail/{username}/{toolId}/{ver}")
fun detail(
@PathVariable username: String,
@PathVariable toolId: String,
@PathVariable ver: String
): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_SELECT_SUCCESS,
data = editService.detail(username, toolId, ver)
)
/**
* Update tool
*
* @param toolUpdateParam Update tool parameters
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolUpdateParam
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "更新工具")
@PutMapping
fun update(@RequestBody @Valid toolUpdateParam: ToolUpdateParam): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS, data = editService.update(toolUpdateParam))
/**
* Submit tool review
*
* @param id Tool ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "提交工具审核")
@PostMapping("/{id}")
fun submit(@PathVariable id: Long): ResponseResult<Nothing> =
if (editService.submit(id)) ResponseResult.success(ResponseCode.TOOL_SUBMIT_SUCCESS)
else ResponseResult.fail(ResponseCode.TOOL_SUBMIT_ERROR)
/**
* Cancel tool review
*
* @param id Tool ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "取消工具审核")
@PutMapping("/{id}")
fun cancel(@PathVariable id: Long): ResponseResult<Nothing> =
if (editService.cancel(id)) ResponseResult.success(ResponseCode.TOOL_CANCEL_SUCCESS)
else ResponseResult.fail(ResponseCode.TOOL_CANCEL_ERROR)
/**
* Delete tool
*
* @param id Tool ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除工具")
@DeleteMapping("/{id}")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (editService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
}

View File

@@ -0,0 +1,132 @@
package top.fatweb.oxygen.api.controller.tool
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.tool.ToolManagementGetParam
import top.fatweb.oxygen.api.param.tool.ToolManagementPassParam
import top.fatweb.oxygen.api.service.tool.IManagementService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
/**
* Tool management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IManagementService
*/
@BaseController(path = ["/system/tool"], name = "工具管理", description = "工具管理相关接口")
class ManagementController(
private val managementService: IManagementService
) {
/**
* Get tool by ID
*
* @param id Tool ID
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "获取单个工具")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:query:tool')")
fun getOne(@PathVariable id: Long): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(data = managementService.getOne(id))
/**
* Get tool paging information
*
* @param toolManagementGetParam Get tool parameters in tool management
* @return Response object includes tool paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolManagementGetParam
* @see ResponseResult
* @see PageVo
* @see ToolVo
*/
@Operation(summary = "获取工具")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:tool:query:tool')")
fun get(toolManagementGetParam: ToolManagementGetParam): ResponseResult<PageVo<ToolVo>> =
ResponseResult.databaseSuccess(data = managementService.getPage(toolManagementGetParam))
/**
* Pass tool review
*
* @param id Tool ID
* @param toolManagementPassParam Pass tool parameters in tool management
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolManagementPassParam
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "通过审核")
@PostMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:modify:tool')")
fun pass(
@PathVariable id: Long,
@RequestBody @Valid toolManagementPassParam: ToolManagementPassParam
): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS,
data = managementService.pass(id, toolManagementPassParam)
)
/**
* Reject tool review
*
* @param id Tool ID
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "驳回审核")
@PutMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:modify:tool')")
fun reject(@PathVariable id: Long): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS, data = managementService.reject(id))
/**
* Put off shelve
*
* @param id Tool ID
* @return Response object includes tool information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolVo
*/
@Operation(summary = "下架")
@PatchMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:modify:tool')")
fun offShelve(@PathVariable id: Long): ResponseResult<ToolVo> =
ResponseResult.databaseSuccess(ResponseCode.DATABASE_UPDATE_SUCCESS, data = managementService.offShelve(id))
/**
* Delete tool
*
* @param id Tool ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除工具")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:delete:tool')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (managementService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
}

View File

@@ -0,0 +1,57 @@
package top.fatweb.oxygen.api.controller.tool
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.PageSortParam
import top.fatweb.oxygen.api.param.tool.ToolStoreGetParam
import top.fatweb.oxygen.api.service.tool.IStoreService
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
/**
* Tool store controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IStoreService
*/
@BaseController(path = ["/tool/store"], name = "工具商店", description = "工具商店相关接口")
class StoreController(
private val storeService: IStoreService
) {
/**
* Get store tool paging information
*
* @param toolStoreGetParam Get tool parameters in tool store
* @return Response object includes store tool paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolStoreGetParam
* @see ResponseResult
* @see PageVo
* @see ToolVo
*/
@GetMapping
fun get(@Valid toolStoreGetParam: ToolStoreGetParam): ResponseResult<PageVo<ToolVo>> =
ResponseResult.databaseSuccess(data = storeService.getPage(toolStoreGetParam))
/**
* Get store tool paging information by username
*
* @param username Username
* @param pageSortParam Page sort parameters
* @return Response object includes store tool paging information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see PageSortParam
* @see ResponseResult
* @see PageVo
* @see ToolVo
*/
@GetMapping("/{username}")
fun get(@PathVariable username: String, @Valid pageSortParam: PageSortParam): ResponseResult<PageVo<ToolVo>> =
ResponseResult.databaseSuccess(data = storeService.getPage(pageSortParam, username))
}

View File

@@ -0,0 +1,112 @@
package top.fatweb.oxygen.api.controller.tool
import io.swagger.v3.oas.annotations.Operation
import jakarta.validation.Valid
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import top.fatweb.oxygen.api.annotation.BaseController
import top.fatweb.oxygen.api.entity.common.ResponseCode
import top.fatweb.oxygen.api.entity.common.ResponseResult
import top.fatweb.oxygen.api.param.tool.ToolTemplateAddParam
import top.fatweb.oxygen.api.param.tool.ToolTemplateUpdateParam
import top.fatweb.oxygen.api.service.tool.IToolTemplateService
import top.fatweb.oxygen.api.vo.tool.ToolTemplateVo
/**
* Tool template management controller
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IToolTemplateService
*/
@BaseController(path = ["/system/tool/template"], name = "工具模板管理", description = "工具模板管理相关接口")
class TemplateController(
private val toolTemplateService: IToolTemplateService
) {
/**
* Get tool template by ID
*
* @param id Tool template ID
* @return Response object includes tool template information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "获取单个模板")
@GetMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:query:template')")
fun getOne(@PathVariable id: Long): ResponseResult<ToolTemplateVo> =
ResponseResult.databaseSuccess(data = toolTemplateService.getOne(id))
/**
* Get tool template list
*
* @return Response object includes tool template list
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "获取模板")
@GetMapping
@PreAuthorize("hasAnyAuthority('system:tool:query:template')")
fun get(): ResponseResult<List<ToolTemplateVo>> =
ResponseResult.databaseSuccess(data = toolTemplateService.get())
/**
* Add tool template
*
* @param toolTemplateAddParam Add tool template parameters
* @return Response object includes tool template information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolTemplateAddParam
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "添加模板")
@PostMapping
@PreAuthorize("hasAnyAuthority('system:tool:add:template')")
fun add(@RequestBody @Valid toolTemplateAddParam: ToolTemplateAddParam): ResponseResult<ToolTemplateVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS,
data = toolTemplateService.add(toolTemplateAddParam)
)
/**
* Update tool template
*
* @param toolTemplateUpdateParam Update tool template parameters
* @return Response object includes tool template information
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolTemplateUpdateParam
* @see ResponseResult
* @see ToolTemplateVo
*/
@Operation(summary = "更新模板")
@PutMapping
@PreAuthorize("hasAnyAuthority('system:tool:modify:template')")
fun update(@RequestBody @Valid toolTemplateUpdateParam: ToolTemplateUpdateParam): ResponseResult<ToolTemplateVo> =
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_UPDATE_SUCCESS,
data = toolTemplateService.update(toolTemplateUpdateParam)
)
/**
* Delete tool template
*
* @param id Tool template ID
* @return Response object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseResult
*/
@Operation(summary = "删除模板")
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyAuthority('system:tool:delete:template')")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> =
if (toolTemplateService.delete(id)) ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
else ResponseResult.databaseFail(ResponseCode.DATABASE_DELETE_FAILED)
}

View File

@@ -0,0 +1,29 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.Func
import top.fatweb.oxygen.api.vo.permission.base.FuncVo
/**
* Function converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object FuncConverter {
/**
* Convert Func object into FuncVo object
*
* @param func Func object
* @return FuncVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Func
* @see FuncVo
*/
fun funcToFuncVo(func: Func) = FuncVo(
id = func.id,
name = func.name,
parentId = func.parentId,
menuId = func.menuId
)
}

View File

@@ -0,0 +1,124 @@
package top.fatweb.oxygen.api.converter.permission
import com.baomidou.mybatisplus.core.metadata.IPage
import top.fatweb.oxygen.api.entity.permission.Group
import top.fatweb.oxygen.api.entity.permission.Role
import top.fatweb.oxygen.api.param.permission.group.GroupAddParam
import top.fatweb.oxygen.api.param.permission.group.GroupUpdateParam
import top.fatweb.oxygen.api.param.permission.group.GroupUpdateStatusParam
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.GroupWithRoleVo
import top.fatweb.oxygen.api.vo.permission.base.GroupVo
/**
* Group converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object GroupConverter {
/**
* Convert Group object into GroupVo object
*
* @param group Group object
* @return GroupVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Group
* @see GroupVo
*/
fun groupToGroupVo(group: Group) = GroupVo(
id = group.id,
name = group.name,
enable = group.enable == 1,
createTime = group.createTime,
updateTime = group.updateTime
)
/**
* Convert Group object into GroupWithRoleVo object
*
* @param group Group object
* @return GroupWithRoleVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Group
* @see GroupWithRoleVo
*/
fun groupToGroupWithRoleVo(group: Group) = GroupWithRoleVo(
id = group.id,
name = group.name,
enable = group.enable == 1,
createTime = group.createTime,
updateTime = group.updateTime,
roles = group.roles?.map(RoleConverter::roleToRoleVo)
)
/**
* Convert IPage<Group> object into PageVo<GroupWithRoleVo> object
*
* @param groupPage IPage<Group> object
* @return PageVo<GroupWithRoleVo> object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IPage
* @see Group
* @see PageVo
* @see GroupWithRoleVo
*/
fun groupPageToGroupWithRolePageVo(groupPage: IPage<Group>) = PageVo(
total = groupPage.total,
pages = groupPage.pages,
size = groupPage.size,
current = groupPage.current,
records = groupPage.records.map(::groupToGroupWithRoleVo)
)
/**
* Convert GroupAddParam object into Group object
*
* @param groupAddParam GroupAddParam object
* @return Group object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupAddParam
* @see Group
*/
fun groupAddParamToGroup(groupAddParam: GroupAddParam) = Group().apply {
name = groupAddParam.name
enable = if (groupAddParam.enable) 1 else 0
roles = groupAddParam.roleIds?.map { Role().apply { id = it } }
}
/**
* Convert GroupUpdateParam object into Group object
*
* @param groupUpdateParam GroupUpdateParam object
* @return Group object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupUpdateParam
* @see Group
*/
fun groupUpdateParamToGroup(groupUpdateParam: GroupUpdateParam) = Group().apply {
id = groupUpdateParam.id
name = groupUpdateParam.name
enable = if (groupUpdateParam.enable) 1 else 0
roles = groupUpdateParam.roleIds?.map { Role().apply { id = it } }
}
/**
* Convert GroupUpdateStatusParam object into Group object
*
* @param groupUpdateStatusParam GroupUpdateStatusParam object
* @return Group object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see GroupUpdateStatusParam
* @see Group
*/
fun groupUpdateStatusParamToGroup(groupUpdateStatusParam: GroupUpdateStatusParam) = Group().apply {
id = groupUpdateStatusParam.id
enable = if (groupUpdateStatusParam.enable) 1 else 0
}
}

View File

@@ -0,0 +1,30 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.Menu
import top.fatweb.oxygen.api.vo.permission.base.MenuVo
/**
* Menu converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object MenuConverter {
/**
* Convert Menu object into MenuVo object
*
* @param menu Menu object
* @return MenuVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Menu
* @see MenuVo
*/
fun menuToMenuVo(menu: Menu) = MenuVo(
id = menu.id,
name = menu.name,
url = menu.url,
parentId = menu.parentId,
moduleId = menu.moduleId
)
}

View File

@@ -0,0 +1,27 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.Module
import top.fatweb.oxygen.api.vo.permission.base.ModuleVo
/**
* Module converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ModuleConverter {
/**
* Convert Module object into ModuleVo object
*
* @param module Module object
* @return ModuleVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Module
* @see ModuleVo
*/
fun moduleToModuleVo(module: Module) = ModuleVo(
id = module.id,
name = module.name
)
}

View File

@@ -0,0 +1,29 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.Operation
import top.fatweb.oxygen.api.vo.permission.base.OperationVo
/**
* Operation converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object OperationConverter {
/**
* Convert Operation object into OperationVo object
*
* @param operation Operation object
* @return OperationVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Operation
* @see OperationVo
*/
fun operationToOperationVo(operation: Operation) = OperationVo(
id = operation.id,
name = operation.name,
code = operation.code,
funcId = operation.funcId
)
}

View File

@@ -0,0 +1,29 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.PowerSet
import top.fatweb.oxygen.api.vo.permission.PowerSetVo
/**
* Power converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object PowerConverter {
/**
* Convert PowerSet object into PowerSetVo object
*
* @param powerSet PowerSet object
* @return PowerSetVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see PowerSet
* @see PowerSetVo
*/
fun powerSetToPowerSetVo(powerSet: PowerSet) = PowerSetVo(
moduleList = powerSet.moduleList?.map(ModuleConverter::moduleToModuleVo),
menuList = powerSet.menuList?.map(MenuConverter::menuToMenuVo),
funcList = powerSet.funcList?.map(FuncConverter::funcToFuncVo),
operationList = powerSet.operationList?.map(OperationConverter::operationToOperationVo)
)
}

View File

@@ -0,0 +1,127 @@
package top.fatweb.oxygen.api.converter.permission
import com.baomidou.mybatisplus.core.metadata.IPage
import top.fatweb.oxygen.api.entity.permission.Power
import top.fatweb.oxygen.api.entity.permission.Role
import top.fatweb.oxygen.api.param.permission.role.RoleAddParam
import top.fatweb.oxygen.api.param.permission.role.RoleUpdateParam
import top.fatweb.oxygen.api.param.permission.role.RoleUpdateStatusParam
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.RoleWithPowerVo
import top.fatweb.oxygen.api.vo.permission.base.RoleVo
/**
* Role converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object RoleConverter {
/**
* Convert Role object into RoleVo object
*
* @param role Role object
* @return RoleVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Role
* @see RoleVo
*/
fun roleToRoleVo(role: Role) = RoleVo(
id = role.id,
name = role.name,
enable = role.enable == 1,
createTime = role.createTime,
updateTime = role.updateTime
)
/**
* Convert Role object into RoleWithPowerVo object
*
* @param role Role object
* @return RoleWithPowerVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Role
* @see RoleWithPowerVo
*/
fun roleToRoleWithPowerVo(role: Role) = RoleWithPowerVo(
id = role.id,
name = role.name,
enable = role.enable == 1,
createTime = role.createTime,
updateTime = role.updateTime,
modules = role.modules?.map(ModuleConverter::moduleToModuleVo),
menus = role.menus?.map(MenuConverter::menuToMenuVo),
funcs = role.funcs?.map(FuncConverter::funcToFuncVo),
operations = role.operations?.map(OperationConverter::operationToOperationVo)
)
/**
* Convert IPage<Role> object into PageVo object
*
* @param rolePage IPage<Role> object
* @return PageVo<RoleWithPowerVo> object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IPage
* @see Role
* @see PageVo
* @see RoleWithPowerVo
*/
fun rolePageToRoleWithPowerPageVo(rolePage: IPage<Role>) = PageVo(
total = rolePage.total,
pages = rolePage.pages,
size = rolePage.size,
current = rolePage.current,
records = rolePage.records.map(::roleToRoleWithPowerVo)
)
/**
* Convert RoleAddParam object into Role object
*
* @param roleAddParam RoleAddParam object
* @return Role object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleAddParam
* @see Role
*/
fun roleAddParamToRole(roleAddParam: RoleAddParam) = Role().apply {
name = roleAddParam.name
enable = if (roleAddParam.enable) 1 else 0
powers = roleAddParam.powerIds?.map { Power().apply { id = it } }
}
/**
* Convert RoleUpdateParam into Role object
*
* @param roleUpdateParam RoleUpdateParam object
* @return Role object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleUpdateParam
* @see Role
*/
fun roleUpdateParamToRole(roleUpdateParam: RoleUpdateParam) = Role().apply {
id = roleUpdateParam.id
name = roleUpdateParam.name
enable = if (roleUpdateParam.enable) 1 else 0
powers = roleUpdateParam.powerIds?.map { Power().apply { id = it } }
}
/**
* Convert RoleUpdateStatusParam object into Role object
*
* @param roleUpdateStatusParam RoleUpdateStatusParam object
* @return Role object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see RoleUpdateStatusParam
* @see Role
*/
fun roleUpdateStatusParamToRole(roleUpdateStatusParam: RoleUpdateStatusParam) = Role().apply {
id = roleUpdateStatusParam.id
enable = if (roleUpdateStatusParam.enable) 1 else 0
}
}

View File

@@ -0,0 +1,213 @@
package top.fatweb.oxygen.api.converter.permission
import com.baomidou.mybatisplus.core.metadata.IPage
import top.fatweb.avatargenerator.GitHubAvatar
import top.fatweb.oxygen.api.entity.permission.Group
import top.fatweb.oxygen.api.entity.permission.Role
import top.fatweb.oxygen.api.entity.permission.User
import top.fatweb.oxygen.api.entity.permission.UserInfo
import top.fatweb.oxygen.api.param.permission.user.UserAddParam
import top.fatweb.oxygen.api.param.permission.user.UserUpdateParam
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.permission.UserWithInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithPowerInfoVo
import top.fatweb.oxygen.api.vo.permission.UserWithRoleInfoVo
/**
* User converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object UserConverter {
/**
* Convert User object into UserWithPowerInfoVo object
*
* @param user User object
* @return UserWithPowerInfoVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see User
* @see UserWithPowerInfoVo
*/
fun userToUserWithPowerInfoVo(user: User) = UserWithPowerInfoVo(
id = user.id,
username = user.username,
verified = user.verify.isNullOrBlank(),
locking = user.locking?.let { it == 1 },
expiration = user.expiration,
credentialsExpiration = user.credentialsExpiration,
enable = user.enable?.let { it == 1 },
currentLoginTime = user.currentLoginTime,
currentLoginIp = user.currentLoginIp,
lastLoginTime = user.lastLoginTime,
lastLoginIp = user.lastLoginIp,
createTime = user.createTime,
updateTime = user.updateTime,
userInfo = user.userInfo?.let(UserInfoConverter::userInfoToUserInfoVo),
modules = user.modules?.map(ModuleConverter::moduleToModuleVo),
menus = user.menus?.map(MenuConverter::menuToMenuVo),
funcs = user.funcs?.map(FuncConverter::funcToFuncVo),
operations = user.operations?.map(OperationConverter::operationToOperationVo)
)
/**
* Convert User object into UserWithRoleInfoVo object
*
* @param user User object
* @return UserWithRoleInfoVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see User
* @see UserWithRoleInfoVo
*/
fun userToUserWithRoleInfoVo(user: User) = UserWithRoleInfoVo(
id = user.id,
username = user.username,
verify = user.verify,
locking = user.locking?.let { it == 1 },
expiration = user.expiration,
credentialsExpiration = user.credentialsExpiration,
enable = user.enable?.let { it == 1 },
currentLoginTime = user.currentLoginTime,
currentLoginIp = user.currentLoginIp,
lastLoginTime = user.lastLoginTime,
lastLoginIp = user.lastLoginIp,
createTime = user.createTime,
updateTime = user.updateTime,
userInfo = user.userInfo?.let(UserInfoConverter::userInfoToUserInfoVo),
roles = user.roles?.map(RoleConverter::roleToRoleVo),
groups = user.groups?.map(GroupConverter::groupToGroupVo)
)
/**
* Convert User object into UserWithInfoVo object
*
* @param user User object
* @return UserWithInfoVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see User
* @see UserWithInfoVo
*/
fun userToUserWithInfoVo(user: User) = UserWithInfoVo(
id = user.id,
username = user.username,
verified = user.verify.isNullOrBlank(),
locking = user.locking?.let { it == 1 },
expiration = user.expiration,
credentialsExpiration = user.credentialsExpiration,
enable = user.enable?.let { it == 1 },
currentLoginTime = user.currentLoginTime,
currentLoginIp = user.currentLoginIp,
lastLoginTime = user.lastLoginTime,
lastLoginIp = user.lastLoginIp,
createTime = user.createTime,
updateTime = user.updateTime,
userInfo = user.userInfo?.let(UserInfoConverter::userInfoToUserInfoVo)
)
/**
* Convert User object into UserWithPasswordRoleInfoVo object
*
* @param user User object
* @return UserWithPasswordRoleInfoVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see User
* @see UserWithPasswordRoleInfoVo
*/
fun userToUserWithPasswordRoleInfoVo(user: User) = UserWithPasswordRoleInfoVo(
id = user.id,
username = user.username,
password = user.password,
verify = user.verify,
locking = user.locking?.let { it == 1 },
expiration = user.expiration,
credentialsExpiration = user.credentialsExpiration,
enable = user.enable?.let { it == 1 },
currentLoginTime = user.currentLoginTime,
currentLoginIp = user.currentLoginIp,
lastLoginTime = user.lastLoginTime,
lastLoginIp = user.lastLoginIp,
createTime = user.createTime,
updateTime = user.updateTime,
userInfo = user.userInfo?.let(UserInfoConverter::userInfoToUserInfoVo),
roles = user.roles?.map(RoleConverter::roleToRoleVo),
groups = user.groups?.map(GroupConverter::groupToGroupVo)
)
/**
* Convert UserAddParam object into User object
*
* @param userAddParam UserAddParam object
* @return User object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserAddParam
* @see User
*/
fun userAddParamToUser(userAddParam: UserAddParam) = User().apply {
username = userAddParam.username
password = userAddParam.password
locking = if (userAddParam.locking) 1 else 0
expiration = userAddParam.expiration
credentialsExpiration = userAddParam.credentialsExpiration
enable = if (userAddParam.enable) 1 else 0
userInfo = UserInfo().apply {
nickname = userAddParam.nickname ?: userAddParam.username
avatar = userAddParam.avatar ?: GitHubAvatar.newAvatarBuilder().build()
.createAsBase64((Long.MIN_VALUE..Long.MAX_VALUE).random())
email = userAddParam.email
}
roles = userAddParam.roleIds?.map { Role().apply { id = it } }
groups = userAddParam.groupIds?.map { Group().apply { id = it } }
}
/**
* Convert UserUpdateParam object into User object
*
* @param userUpdateParam UserUpdateParam object
* @return User object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserUpdateParam
* @see User
*/
fun userUpdateParamToUser(userUpdateParam: UserUpdateParam) = User().apply {
id = userUpdateParam.id
username = userUpdateParam.username
locking = if (userUpdateParam.locking && userUpdateParam.id != 0L) 1 else 0
expiration = if (userUpdateParam.id != 0L) userUpdateParam.expiration else null
credentialsExpiration = userUpdateParam.credentialsExpiration
enable = if (userUpdateParam.enable || userUpdateParam.id == 0L) 1 else 0
userInfo = UserInfo().apply {
nickname = userUpdateParam.nickname
avatar = userUpdateParam.avatar
email = userUpdateParam.email
}
roles = if (userUpdateParam.id != 0L) userUpdateParam.roleIds?.map { Role().apply { id = it } } else null
groups = if (userUpdateParam.id != 0L) userUpdateParam.groupIds?.map { Group().apply { id = it } } else null
}
/**
* Convert IPage<User> object into PageVo<UserWithRoleInfoVo> object
*
* @param userPage IPage<User> object
* @return PageVo<UserWithRoleInfoVo> object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IPage
* @see User
* @see PageVo
* @see UserWithRoleInfoVo
*/
fun userPageToUserWithRoleInfoPageVo(userPage: IPage<User>) = PageVo(
total = userPage.total,
pages = userPage.pages,
size = userPage.size,
current = userPage.current,
records = userPage.records.map(::userToUserWithRoleInfoVo)
)
}

View File

@@ -0,0 +1,32 @@
package top.fatweb.oxygen.api.converter.permission
import top.fatweb.oxygen.api.entity.permission.UserInfo
import top.fatweb.oxygen.api.vo.permission.base.UserInfoVo
/**
* User information converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object UserInfoConverter {
/**
* Convert UserInfo object into UserInfoVo object
*
* @param userInfo UserInfo object
* @return UserInfoVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserInfo
* @see UserInfoVo
*/
fun userInfoToUserInfoVo(userInfo: UserInfo) = UserInfoVo(
id = userInfo.id,
userId = userInfo.userId,
nickname = userInfo.nickname,
avatar = userInfo.avatar,
email = userInfo.email,
createTime = userInfo.createTime,
updateTime = userInfo.updateTime
)
}

View File

@@ -0,0 +1,46 @@
package top.fatweb.oxygen.api.converter.system
import top.fatweb.oxygen.api.entity.system.SensitiveWord
import top.fatweb.oxygen.api.param.system.SensitiveWordAddParam
import top.fatweb.oxygen.api.vo.system.SensitiveWordVo
/**
* Settings converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object SettingsConverter {
/**
* Convert SensitiveWord object into SensitiveWordVo object
*
* @param sensitiveWord SensitiveWord object
* @return SensitiveWordVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SensitiveWord
* @see SensitiveWordVo
*/
fun sensitiveWordToSensitiveWordVo(sensitiveWord: SensitiveWord) = SensitiveWordVo(
id = sensitiveWord.id,
word = sensitiveWord.word,
useFor = sensitiveWord.useFor?.map(SensitiveWord.Use::valueOf)?.toSet(),
enable = sensitiveWord.enable == 1
)
/**
* Convert SensitiveWordAddParam object into SensitiveWord object
*
* @param sensitiveWordAddParam SensitiveWordAddParam object
* @return SensitiveWord object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see SensitiveWordAddParam
* @see SensitiveWord
*/
fun sensitiveWordAddParamToSensitiveWord(sensitiveWordAddParam: SensitiveWordAddParam) = SensitiveWord().apply {
word = sensitiveWordAddParam.word
useFor = sensitiveWordAddParam.useFor.map(SensitiveWord.Use::code).toSet()
enable = if (sensitiveWordAddParam.enable) 1 else 0
}
}

View File

@@ -0,0 +1,53 @@
package top.fatweb.oxygen.api.converter.system
import com.baomidou.mybatisplus.core.metadata.IPage
import top.fatweb.oxygen.api.entity.system.SysLog
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.system.SysLogVo
/**
* System log converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object SysLogConverter {
/**
* Convert IPage<SysLog> object into PageVo<SysLogVo> object
*
* @param syslogPage IPage<Syslog> object
* @return PageVo<SysLogVo> object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see IPage
* @see SysLog
* @see PageVo
* @see SysLogVo
*/
fun sysLogPageToSysLogPageVo(syslogPage: IPage<SysLog>) = PageVo(
syslogPage.total,
syslogPage.pages,
syslogPage.size,
syslogPage.current,
syslogPage.records.map {
SysLogVo(
id = it.id,
logType = it.logType,
operateUserId = it.operateUserId,
operateTime = it.operateTime,
requestUri = it.requestUri,
requestMethod = it.requestMethod,
requestParams = it.requestParams,
requestIp = it.requestIp,
requestServerAddress = it.requestServerAddress,
exception = it.exception == 1,
exceptionInfo = it.exceptionInfo,
startTime = it.startTime,
endTime = it.endTime,
executeTime = it.executeTime,
userAgent = it.userAgent,
operateUsername = it.operateUsername
)
})
}

View File

@@ -0,0 +1,53 @@
package top.fatweb.oxygen.api.converter.tool
import top.fatweb.oxygen.api.entity.tool.ToolBase
import top.fatweb.oxygen.api.vo.tool.ToolBaseVo
import top.fatweb.oxygen.api.vo.tool.ToolDataVo
/**
* Tool base converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ToolBaseConverter {
/**
* Convert ToolBase object into ToolBaseVo object
*
* @param toolBase ToolBase object
* @return ToolBaseVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBase
* @see ToolBaseVo
*/
fun toolBaseToToolBaseVo(toolBase: ToolBase) = ToolBaseVo(
id = toolBase.id,
name = toolBase.name,
source = toolBase.source?.let(ToolDataConverter::toolDataToToolDataVo),
dist = toolBase.dist?.let(ToolDataConverter::toolDataToToolDataVo),
compiled = toolBase.compiled == 1,
createTime = toolBase.createTime,
updateTime = toolBase.updateTime
)
/**
* Convert ToolBase object into ToolBaseVo object by get list
*
* @param toolBase ToolBase object
* @return ToolBaseVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolBase
* @see ToolBaseVo
*/
fun toolBaseToToolBaseVoByGetList(toolBase: ToolBase) = ToolBaseVo(
id = toolBase.id,
name = toolBase.name,
source = ToolDataVo(id = toolBase.sourceId, data = null, createTime = null, updateTime = null),
dist = ToolDataVo(id = toolBase.distId, data = null, createTime = null, updateTime = null),
compiled = toolBase.compiled == 1,
createTime = toolBase.createTime,
updateTime = toolBase.updateTime
)
}

View File

@@ -0,0 +1,63 @@
package top.fatweb.oxygen.api.converter.tool
import top.fatweb.oxygen.api.entity.tool.ToolCategory
import top.fatweb.oxygen.api.param.tool.ToolCategoryAddParam
import top.fatweb.oxygen.api.param.tool.ToolCategoryUpdateParam
import top.fatweb.oxygen.api.vo.tool.ToolCategoryVo
/**
* Tool category converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ToolCategoryConverter {
/**
* Convert ToolCategory object into ToolCategoryVo object
*
* @param toolCategory ToolCategory object
* @return ToolCategoryVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCategory
* @see ToolCategoryVo
*/
fun toolCategoryToToolCategoryVo(toolCategory: ToolCategory) = ToolCategoryVo(
id = toolCategory.id,
name = toolCategory.name,
enable = toolCategory.enable == 1,
createTime = toolCategory.createTime,
updateTime = toolCategory.updateTime
)
/**
* Convert ToolCategoryAddParam object into ToolCategory object
*
* @param toolCategoryAddParam ToolCategoryAddParam object
* @return ToolCateGory object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCategoryAddParam
* @see ToolCategory
*/
fun toolCategoryAddParamToToolCategory(toolCategoryAddParam: ToolCategoryAddParam) = ToolCategory().apply {
name = toolCategoryAddParam.name
enable = if (toolCategoryAddParam.enable) 1 else 0
}
/**
* Convert ToolCategoryUpdateParam object into ToolCategory object
*
* @param toolCategoryUpdateParam ToolCategoryUpdateParam object
* @return ToolCategory object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolCategoryUpdateParam
* @see ToolCategory
*/
fun toolCategoryUpdateParamToToolCategory(toolCategoryUpdateParam: ToolCategoryUpdateParam) = ToolCategory().apply {
id = toolCategoryUpdateParam.id
name = toolCategoryUpdateParam.name
enable = toolCategoryUpdateParam.enable?. let { if (it) 1 else 0 }
}
}

View File

@@ -0,0 +1,65 @@
package top.fatweb.oxygen.api.converter.tool
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import top.fatweb.oxygen.api.converter.permission.UserConverter
import top.fatweb.oxygen.api.entity.tool.Tool
import top.fatweb.oxygen.api.vo.PageVo
import top.fatweb.oxygen.api.vo.tool.ToolVo
/**
* Tool converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ToolConverter {
/**
* Convert Tool object into ToolVo object
*
* @param tool Tool object
* @return ToolVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Tool
* @see ToolVo
*/
fun toolToToolVo(tool: Tool) = ToolVo(
id = tool.id,
name = tool.name,
toolId = tool.toolId,
icon = tool.icon,
description = tool.description,
base = tool.base?.let(ToolBaseConverter::toolBaseToToolBaseVo),
author = tool.author?.let(UserConverter::userToUserWithInfoVo),
ver = tool.ver,
keywords = tool.keywords,
categories = tool.categories?.map(ToolCategoryConverter::toolCategoryToToolCategoryVo),
source = tool.source?.let(ToolDataConverter::toolDataToToolDataVo),
dist = tool.dist?.let(ToolDataConverter::toolDataToToolDataVo),
entryPoint = tool.entryPoint,
publish = tool.publish,
review = tool.review,
createTime = tool.createTime,
updateTime = tool.updateTime
)
/**
* Convert Page<Tool> object into PageVo<ToolVo> object
*
* @param toolPage Page<Tool> object
* @return PageVo<ToolVo> object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Page
* @see Tool
* @see PageVo
* @see ToolVo
*/
fun toolPageToToolPageVo(toolPage: Page<Tool>): PageVo<ToolVo> = PageVo(
total = toolPage.total,
pages = toolPage.pages,
size = toolPage.size,
current = toolPage.current,
records = toolPage.records.map(::toolToToolVo)
)
}

View File

@@ -0,0 +1,29 @@
package top.fatweb.oxygen.api.converter.tool
import top.fatweb.oxygen.api.entity.tool.ToolData
import top.fatweb.oxygen.api.vo.tool.ToolDataVo
/**
* Tool data converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ToolDataConverter {
/**
* Convert ToolData object into ToolDataVo object
*
* @param toolData ToolData object
* @return ToolDataVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolData
* @see ToolDataVo
*/
fun toolDataToToolDataVo(toolData: ToolData) = ToolDataVo(
id = toolData.id,
data = toolData.data,
createTime = toolData.createTime,
updateTime = toolData.updateTime
)
}

View File

@@ -0,0 +1,85 @@
package top.fatweb.oxygen.api.converter.tool
import top.fatweb.oxygen.api.entity.tool.ToolTemplate
import top.fatweb.oxygen.api.vo.tool.ToolBaseVo
import top.fatweb.oxygen.api.vo.tool.ToolDataVo
import top.fatweb.oxygen.api.vo.tool.ToolTemplateVo
/**
* Tool template converter
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object ToolTemplateConverter {
/**
* Convert ToolTemplate object into ToolTemplateVo object
*
* @param toolTemplate ToolTemplate object
* @return ToolTemplateVo object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ToolTemplate
* @see ToolTemplateVo
*/
fun toolTemplateToToolTemplateVo(toolTemplate: ToolTemplate) = ToolTemplateVo(
id = toolTemplate.id,
name = toolTemplate.name,
base = toolTemplate.base?.let(ToolBaseConverter::toolBaseToToolBaseVo),
source = toolTemplate.source?.let(ToolDataConverter::toolDataToToolDataVo),
entryPoint = toolTemplate.entryPoint,
enable = toolTemplate.enable == 1,
createTime = toolTemplate.createTime,
updateTime = toolTemplate.updateTime
)
/**
* Convert ToolTemplate object into ToolTemplateVo object by list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun toolTemplateToToolTemplateVoByList(toolTemplate: ToolTemplate) = ToolTemplateVo(
id = toolTemplate.id,
name = toolTemplate.name,
base = ToolBaseVo(
id = toolTemplate.baseId,
name = null,
source = null,
dist = null,
compiled = null,
createTime = null,
updateTime = null
),
source = ToolDataVo(id = toolTemplate.sourceId, data = null, createTime = null, updateTime = null),
entryPoint = toolTemplate.entryPoint,
enable = toolTemplate.enable == 1,
createTime = toolTemplate.createTime,
updateTime = toolTemplate.updateTime
)
/**
* Convert ToolTemplate object into ToolTemplateVo object with base dist
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun toolTemplateToToolTemplateVoWithBaseDist(toolTemplate: ToolTemplate) = ToolTemplateVo(
id = toolTemplate.id,
name = toolTemplate.name,
base = ToolBaseVo(
id = toolTemplate.baseId,
name = toolTemplate.base?.name,
source = null,
dist = ToolDataVo(id = null, data = toolTemplate.base?.distData, createTime = null, updateTime = null),
compiled = null,
createTime = null,
updateTime = null
),
source = toolTemplate.source?.let(ToolDataConverter::toolDataToToolDataVo),
entryPoint = toolTemplate.entryPoint,
enable = toolTemplate.enable == 1,
createTime = toolTemplate.createTime,
updateTime = toolTemplate.updateTime
)
}

View File

@@ -0,0 +1,35 @@
package top.fatweb.oxygen.api.cron
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import top.fatweb.oxygen.api.entity.system.StatisticsLog
import top.fatweb.oxygen.api.properties.SecurityProperties
import top.fatweb.oxygen.api.service.system.IStatisticsLogService
import top.fatweb.oxygen.api.util.RedisUtil
/**
* Statistics scheduled tasks
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Component
class StatisticsCron(
private val redisUtil: RedisUtil,
private val statisticsLogService: IStatisticsLogService
) {
/**
* Auto record number of online users
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Scheduled(cron = "0 * * * * *")
fun onlineUserCount() {
statisticsLogService.save(StatisticsLog().apply {
key = StatisticsLog.KeyItem.ONLINE_USERS_COUNT
value = redisUtil.keys("${SecurityProperties.jwtIssuer}_login_*")
.distinctBy { Regex("${SecurityProperties.jwtIssuer}_login_(.*):.*").matchEntire(it)?.groupValues?.getOrNull(1) }.size.toString()
})
}
}

View File

@@ -0,0 +1,49 @@
package top.fatweb.oxygen.api.entity.common
/**
* Business code entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
enum class BusinessCode(val code: Int) {
/**
* System
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
SYSTEM(100),
/**
* Permission
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
PERMISSION(200),
/**
* Database
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
DATABASE(300),
/**
* Tool
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
TOOL(400),
/**
* Avatar API
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
API_AVATAR(501)
}

View File

@@ -0,0 +1,76 @@
package top.fatweb.oxygen.api.entity.common
/**
* Response code entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
enum class ResponseCode(val code: Int) {
SYSTEM_OK(BusinessCode.SYSTEM, 0),
SYSTEM_ERROR(BusinessCode.SYSTEM, 50),
SYSTEM_TIMEOUT(BusinessCode.SYSTEM, 51),
SYSTEM_REQUEST_ILLEGAL(BusinessCode.SYSTEM, 52),
SYSTEM_ARGUMENT_NOT_VALID(BusinessCode.SYSTEM, 53),
SYSTEM_INVALID_CAPTCHA_CODE(BusinessCode.SYSTEM, 54),
SYSTEM_REQUEST_TOO_FREQUENT(BusinessCode.SYSTEM, 55),
SYSTEM_MATCH_SENSITIVE_WORD(BusinessCode.SYSTEM, 56),
PERMISSION_LOGIN_SUCCESS(BusinessCode.PERMISSION, 0),
PERMISSION_PASSWORD_CHANGE_SUCCESS(BusinessCode.PERMISSION, 1),
PERMISSION_LOGOUT_SUCCESS(BusinessCode.PERMISSION, 2),
PERMISSION_TOKEN_RENEW_SUCCESS(BusinessCode.PERMISSION, 3),
PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4),
PERMISSION_RESEND_SUCCESS(BusinessCode.PERMISSION, 5),
PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 6),
PERMISSION_FORGET_SUCCESS(BusinessCode.PERMISSION, 7),
PERMISSION_RETRIEVE_SUCCESS(BusinessCode.PERMISSION, 8),
PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50),
PERMISSION_USERNAME_NOT_FOUND(BusinessCode.PERMISSION, 51),
PERMISSION_ACCESS_DENIED(BusinessCode.PERMISSION, 52),
PERMISSION_USER_LOCKED(BusinessCode.PERMISSION, 53),
PERMISSION_USER_EXPIRED(BusinessCode.PERMISSION, 54),
PERMISSION_USER_CREDENTIALS_EXPIRED(BusinessCode.PERMISSION, 55),
PERMISSION_USER_DISABLE(BusinessCode.PERMISSION, 56),
PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR(BusinessCode.PERMISSION, 57),
PERMISSION_OLD_PASSWORD_NOT_MATCH(BusinessCode.PERMISSION, 58),
PERMISSION_LOGOUT_FAILED(BusinessCode.PERMISSION, 59),
PERMISSION_TOKEN_ILLEGAL(BusinessCode.PERMISSION, 60),
PERMISSION_TOKEN_HAS_EXPIRED(BusinessCode.PERMISSION, 61),
PERMISSION_NO_VERIFICATION_REQUIRED(BusinessCode.PERMISSION, 62),
PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 63),
PERMISSION_ACCOUNT_NEED_INIT(BusinessCode.PERMISSION, 64),
PERMISSION_USER_NOT_FOUND(BusinessCode.PERMISSION, 65),
PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 66),
PERMISSION_ACCOUNT_NEED_RESET_PASSWORD(BusinessCode.PERMISSION, 67),
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),
DATABASE_INSERT_SUCCESS(BusinessCode.DATABASE, 10),
DATABASE_INSERT_FAILED(BusinessCode.DATABASE, 15),
DATABASE_UPDATE_SUCCESS(BusinessCode.DATABASE, 20),
DATABASE_UPDATE_FAILED(BusinessCode.DATABASE, 25),
DATABASE_DELETE_SUCCESS(BusinessCode.DATABASE, 30),
DATABASE_DELETE_FAILED(BusinessCode.DATABASE, 35),
DATABASE_EXECUTE_ERROR(BusinessCode.DATABASE, 50),
DATABASE_DUPLICATE_KEY(BusinessCode.DATABASE, 51),
DATABASE_NO_RECORD_FOUND(BusinessCode.DATABASE, 52),
TOOL_SUBMIT_SUCCESS(BusinessCode.TOOL, 10),
TOOL_CANCEL_SUCCESS(BusinessCode.TOOL, 11),
TOOL_ILLEGAL_VERSION(BusinessCode.TOOL, 50),
TOOL_UNDER_REVIEW(BusinessCode.TOOL, 51),
TOOL_NOT_UNDER_REVIEW(BusinessCode.TOOL, 52),
TOOL_HAS_UNPUBLISHED_VERSION(BusinessCode.TOOL, 53),
TOOL_HAS_NOT_BEEN_PUBLISHED(BusinessCode.TOOL, 54),
TOOL_HAS_BEEN_PUBLISHED(BusinessCode.TOOL, 55),
TOOL_SUBMIT_ERROR(BusinessCode.TOOL, 60),
TOOL_CANCEL_ERROR(BusinessCode.TOOL, 61),
API_AVATAR_SUCCESS(BusinessCode.API_AVATAR, 0),
API_AVATAR_ERROR(BusinessCode.API_AVATAR, 50);
constructor(businessCode: BusinessCode, code: Int) : this(businessCode.code * 100 + code)
}

View File

@@ -0,0 +1,103 @@
package top.fatweb.oxygen.api.entity.common
import io.swagger.v3.oas.annotations.media.Schema
import java.io.Serializable
/**
* Response result entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class ResponseResult<T> private constructor(
@Schema(description = "响应码", defaultValue = "200") val code: Int,
@Schema(description = "是否调用成功") val success: Boolean,
@Schema(description = "信息") val msg: String,
@Schema(description = "数据") val data: T?
) : Serializable {
companion object {
/**
* Build response result object
*
* @param code Response code
* @param success Is successful
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun <T> build(code: Int, success: Boolean, msg: String, data: T?) =
ResponseResult(code, success, msg, data)
/**
* Build response result object
*
* @param code Response code object
* @param success Is successful
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseCode
*/
fun <T> build(code: ResponseCode, success: Boolean, msg: String, data: T?) =
build(code.code, success, msg, data)
/**
* Build successful response result object
*
* @param code Response code object
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseCode
*/
fun <T> success(code: ResponseCode = ResponseCode.SYSTEM_OK, msg: String = "success", data: T? = null) =
build(code, true, msg, data)
/**
* Build failure response result object
*
* @param code Response code object
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseCode
*/
fun <T> fail(code: ResponseCode = ResponseCode.SYSTEM_ERROR, msg: String = "fail", data: T? = null) =
build(code, false, msg, data)
/**
* Build database successful response result object
*
* @param code Response code object
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseCode
*/
fun <T> databaseSuccess(
code: ResponseCode = ResponseCode.DATABASE_SELECT_SUCCESS, msg: String = "success", data: T? = null
) = build(code, true, msg, data)
/**
* Build database failure response result object
*
* @param code Response code object
* @param msg Response message
* @param data Response data
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see ResponseCode
*/
fun <T> databaseFail(
code: ResponseCode = ResponseCode.DATABASE_SELECT_FAILED, msg: String = "fail", data: T? = null
) = build(code, false, msg, data)
}
}

View File

@@ -0,0 +1,55 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Function entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_func")
class Func : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
/**
* Parent ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("parent_id")
var parentId: Long? = null
/**
* Menu ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("menu_id")
var menuId: Long? = null
override fun toString(): String {
return "Func(id=$id, name=$name, parentId=$parentId, menuId=$menuId)"
}
}

View File

@@ -0,0 +1,95 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
import java.time.LocalDateTime
/**
* Group entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_group")
class Group : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
/**
* Enable
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("enable")
var enable: Int? = null
/**
* Create time
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see LocalDateTime
*/
@TableField("create_time", fill = FieldFill.INSERT)
var createTime: LocalDateTime? = null
/**
* Update time
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see LocalDateTime
*/
@TableField("update_time", fill = FieldFill.INSERT_UPDATE)
var updateTime: LocalDateTime? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
/**
* Role list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Role
*/
@TableField(exist = false)
var roles: List<Role>? = null
override fun toString(): String {
return "Group(id=$id, name=$name, enable=$enable, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, roles=$roles)"
}
}

View File

@@ -0,0 +1,67 @@
package top.fatweb.oxygen.api.entity.permission
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonTypeInfo
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import java.time.LocalDateTime
import java.time.ZoneOffset
/**
* Login user entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see UserDetails
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
class LoginUser() : UserDetails {
/**
* User object
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see User
*/
lateinit var user: User
@JsonIgnore
private var authorities: List<GrantedAuthority>? = null
constructor(user: User) : this() {
this.user = user
}
@JsonIgnore
override fun getAuthorities(): List<GrantedAuthority> {
authorities?.let { return it }
authorities = user.operations?.map { SimpleGrantedAuthority(it.code) } ?: emptyList()
return authorities as List<GrantedAuthority>
}
@JsonIgnore
override fun getPassword() = user.password
@JsonIgnore
override fun getUsername() = user.username
@JsonIgnore
override fun isAccountNonExpired() =
user.expiration == null || user.expiration!!.isAfter(LocalDateTime.now(ZoneOffset.UTC))
@JsonIgnore
override fun isAccountNonLocked() = user.locking == 0
@JsonIgnore
override fun isCredentialsNonExpired() =
user.credentialsExpiration == null || user.credentialsExpiration!!.isAfter(LocalDateTime.now(ZoneOffset.UTC))
@JsonIgnore
override fun isEnabled() = user.enable == 1
override fun toString(): String {
return "LoginUser(user=$user, authorities=$authorities)"
}
}

View File

@@ -0,0 +1,64 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Menu entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_menu")
class Menu : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
/**
* URL
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("url")
var url: String? = null
/**
* Parent ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("parent_id")
var parentId: Long? = null
/**
* Module ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("module_id")
var moduleId: Long? = null
override fun toString(): String {
return "Menu(id=$id, name=$name, url=$url, parentId=$parentId, moduleId=$moduleId)"
}
}

View File

@@ -0,0 +1,37 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Module Entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_module")
class Module : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
override fun toString(): String {
return "Module(id=$id, name=$name)"
}
}

View File

@@ -0,0 +1,55 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Operation entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_operation")
class Operation : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
/**
* Code
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("code")
var code: String? = null
/**
* Function ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("func_id")
var funcId: Long? = null
override fun toString(): String {
return "Operation(id=$id, name=$name, code=$code, funcId=$funcId)"
}
}

View File

@@ -0,0 +1,37 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Power entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_power")
class Power : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Type ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("type_id")
var typeId: Int? = null
override fun toString(): String {
return "Power(id=$id, typeId=$typeId)"
}
}

View File

@@ -0,0 +1,51 @@
package top.fatweb.oxygen.api.entity.permission
import java.io.Serializable
/**
* Set of power entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class PowerSet : Serializable {
/**
* Module list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Module
*/
var moduleList: List<Module>? = null
/**
* Menu list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Menu
*/
var menuList: List<Menu>? = null
/**
* Function list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Func
*/
var funcList: List<Func>? = null
/**
* Operation list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Operation
*/
var operationList: List<Operation>? = null
override fun toString(): String {
return "PowerSet(moduleList=$moduleList, menuList=$menuList, funcList=$funcList, operationList=$operationList)"
}
}

View File

@@ -0,0 +1,37 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableName
import java.io.Serializable
/**
* Power type entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_power_type")
class PowerType : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
override fun toString(): String {
return "PowerType(id=$id, name=$name)"
}
}

View File

@@ -0,0 +1,64 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
/**
* Power role intermediate entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_r_power_role")
class RPowerRole : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Power ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("power_id")
var powerId: Long? = null
/**
* Role ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("role_id")
var roleId: Long? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "RPowerRole(id=$id, powerId=$powerId, roleId=$roleId, deleted=$deleted, version=$version)"
}
}

View File

@@ -0,0 +1,64 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
/**
* Role group intermediate entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_r_role_group")
class RRoleGroup : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Role ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("role_id")
var roleId: Long? = null
/**
* Group ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("group_id")
var groupId: Long? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "RRoleGroup(id=$id, roleId=$roleId, groupId=$groupId, deleted=$deleted, version=$version)"
}
}

View File

@@ -0,0 +1,64 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
/**
* User group intermediate entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_r_user_group")
class RUserGroup : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* User ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("user_id")
var userId: Long? = null
/**
* Group ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("group_id")
var groupId: Long? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "RUserGroup(id=$id, userId=$userId, groupId=$groupId, deleted=$deleted, version=$version)"
}
}

View File

@@ -0,0 +1,64 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
/**
* User role intermediate entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_r_user_role")
class RUserRole : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* User ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("user_id")
var userId: Long? = null
/**
* Role ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("role_id")
var roleId: Long? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
override fun toString(): String {
return "RUserRole(id=$id, userId=$userId, roleId=$roleId, deleted=$deleted, version=$version)"
}
}

View File

@@ -0,0 +1,135 @@
package top.fatweb.oxygen.api.entity.permission
import com.baomidou.mybatisplus.annotation.*
import java.io.Serializable
import java.time.LocalDateTime
/**
* Role entity
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableName("t_s_role")
class Role : Serializable {
/**
* ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableId("id")
var id: Long? = null
/**
* Name
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("name")
var name: String? = null
/**
* Enable
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("enable")
var enable: Int? = null
/**
* Create time
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see LocalDateTime
*/
@TableField("create_time", fill = FieldFill.INSERT)
var createTime: LocalDateTime? = null
/**
* Update time
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see LocalDateTime
*/
@TableField("update_time", fill = FieldFill.INSERT_UPDATE)
var updateTime: LocalDateTime? = null
/**
* Deleted
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("deleted")
@TableLogic
var deleted: Long? = null
/**
* Version
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("version")
@Version
var version: Int? = null
/**
* Module list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Module
*/
@TableField(exist = false)
var modules: List<Module>? = null
/**
* Menu list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Menu
*/
@TableField(exist = false)
var menus: List<Menu>? = null
/**
* Function list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Func
*/
@TableField(exist = false)
var funcs: List<Func>? = null
/**
* Operation list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Operation
*/
@TableField(exist = false)
var operations: List<Operation>? = null
/**
* Power list
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see Power
*/
@TableField(exist = false)
var powers: List<Power>? = null
override fun toString(): String {
return "Role(id=$id, name=$name, enable=$enable, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, modules=$modules, menus=$menus, funcs=$funcs, operations=$operations, powers=$powers)"
}
}

Some files were not shown because too many files have changed in this diff Show More