first commit.
commit
e916d067f3
|
@ -0,0 +1,36 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
|
||||||
|
[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.svg]
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.js.map]
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.less]
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.vue]
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
[{.analysis_options,*.yml,*.yaml}]
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.java]
|
||||||
|
indent_size = 4
|
|
@ -0,0 +1,34 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
*/**/http/*.http
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
|
@ -0,0 +1,21 @@
|
||||||
|
# PlusoneAdmin
|
||||||
|
|
||||||
|
后台管理系统 +1
|
||||||
|
|
||||||
|
本仓库是后端部分,使用 Spring Boot 2.X 进行开发,对前端提供接口。
|
||||||
|
|
||||||
|
简单落地 DDD。
|
||||||
|
|
||||||
|
- JDK 17
|
||||||
|
|
||||||
|
- 主要使用 Spring JDBC 的 NamedParameterJdbcTemplate 进行数据的查询和持久化,因为感觉有更多的自由度。可以根据 ResultSet,任意按照需要的方式实例化 Entity。MyBatis 在某些地方用于查询。
|
||||||
|
|
||||||
|
- 数据库使用的是 PostgreSQL;
|
||||||
|
|
||||||
|
- 权限管理使用的是 Sa-Token,使用 Redis 共享 Session。
|
||||||
|
|
||||||
|
- 短信服务使用的是腾讯云的 SMS 服务。后续将其解耦,使实现等各方面可替换为阿里云或其它 SMS 服务。
|
||||||
|
|
||||||
|
目前项目还没完成,开发中……
|
||||||
|
|
||||||
|
相关的文档和介绍完善中……
|
|
@ -0,0 +1,774 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"code": 2000000,
|
||||||
|
"description": "正常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4000000,
|
||||||
|
"description": "用户端错误",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4010000,
|
||||||
|
"description": "用户注册错误",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4010100,
|
||||||
|
"description": "用户未同意隐私协议"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010200,
|
||||||
|
"description": "注册国家或地区受限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010300,
|
||||||
|
"description": "邮箱和手机号应至少绑定一个"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010400,
|
||||||
|
"description": "用户名已存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010500,
|
||||||
|
"description": "邮箱已存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010600,
|
||||||
|
"description": "手机号已存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010700,
|
||||||
|
"description": "校验码异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4010701,
|
||||||
|
"description": "校验码输入错误"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010702,
|
||||||
|
"description": "校验码不存在或过期"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010800,
|
||||||
|
"description": "用户证件异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4010801,
|
||||||
|
"description": "用户证件类型未选择"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010802,
|
||||||
|
"description": "大陆身份证编号校验非法"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010803,
|
||||||
|
"description": "护照编号校验非法"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4010804,
|
||||||
|
"description": "军官证编号校验非法"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020000,
|
||||||
|
"description": "用户登录异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4020100,
|
||||||
|
"description": "用户账户异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4020101,
|
||||||
|
"description": "用户账户不存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020102,
|
||||||
|
"description": "用户账户被冻结"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020103,
|
||||||
|
"description": "用户账户已作废"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020200,
|
||||||
|
"description": "用户密码错误",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4020201,
|
||||||
|
"description": "用户输入密码错误次数超限"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020300,
|
||||||
|
"description": "用户身份校验失败",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4020301,
|
||||||
|
"description": "用户指纹识别失败"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020302,
|
||||||
|
"description": "用户面容识别失败"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020303,
|
||||||
|
"description": "用户未获得第三方登录授权"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020400,
|
||||||
|
"description": "用户登录已过期"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020500,
|
||||||
|
"description": "用户验证码错误",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4020501,
|
||||||
|
"description": "用户验证码输入错误"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4020502,
|
||||||
|
"description": "用户验证码不存在或已过期"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030000,
|
||||||
|
"description": "访问权限异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4030100,
|
||||||
|
"description": "访问未授权",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4030101,
|
||||||
|
"description": "因访问对象隐私设置被拦截"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030102,
|
||||||
|
"description": "授权已过期"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030103,
|
||||||
|
"description": "无权限使用 API"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030200,
|
||||||
|
"description": "用户访问被拦截",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4030201,
|
||||||
|
"description": "黑名单用户"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030202,
|
||||||
|
"description": "账号被冻结"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030203,
|
||||||
|
"description": "非法 IP 地址"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030204,
|
||||||
|
"description": "网关访问受限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030205,
|
||||||
|
"description": "地域黑名单"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030300,
|
||||||
|
"description": "服务已欠费"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4030400,
|
||||||
|
"description": "用户签名异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4030401,
|
||||||
|
"description": "RSA 签名错误"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040000,
|
||||||
|
"description": "用户请求参数错误",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040100,
|
||||||
|
"description": "包含非法恶意跳转链接"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040200,
|
||||||
|
"description": "无效的用户输入",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040201,
|
||||||
|
"description": "不支持的 PrincipalType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040300,
|
||||||
|
"description": "请求必填参数为空",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040301,
|
||||||
|
"description": "用户订单号为空"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040302,
|
||||||
|
"description": "订购数量为空"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040303,
|
||||||
|
"description": "缺少时间戳参数"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040304,
|
||||||
|
"description": "非法的时间戳参数"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040400,
|
||||||
|
"description": "请求参数值超出允许的范围",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040401,
|
||||||
|
"description": "参数格式不匹配"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040402,
|
||||||
|
"description": "地址不在服务范围"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040403,
|
||||||
|
"description": "时间不在服务范围"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040404,
|
||||||
|
"description": "金额超出限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040405,
|
||||||
|
"description": "数量超出限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040406,
|
||||||
|
"description": "请求批量处理总个数超出限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040407,
|
||||||
|
"description": "请求 JSON 解析失败"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040500,
|
||||||
|
"description": "用户输入内容非法",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040501,
|
||||||
|
"description": "包含违禁敏感词"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040502,
|
||||||
|
"description": "图片包含违禁信息"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040503,
|
||||||
|
"description": "文件侵犯版权"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040600,
|
||||||
|
"description": "用户操作异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4040601,
|
||||||
|
"description": "用户支付超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040602,
|
||||||
|
"description": "确认订单超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4040603,
|
||||||
|
"description": "订单已关闭"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050000,
|
||||||
|
"description": "用户请求服务异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4050100,
|
||||||
|
"description": "请求次数超出限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050200,
|
||||||
|
"description": "请求并发数超出限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050300,
|
||||||
|
"description": "用户操作请等待"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050400,
|
||||||
|
"description": "WebSocket 连接异常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050500,
|
||||||
|
"description": "WebSocket 连接断开"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4050600,
|
||||||
|
"description": "用户重复请求"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4060000,
|
||||||
|
"description": "用户资源异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4060100,
|
||||||
|
"description": "账户余额不足"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4060200,
|
||||||
|
"description": "用户磁盘空间不足"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4060300,
|
||||||
|
"description": "用户内存空间不足"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4060400,
|
||||||
|
"description": "用户 OSS 容量不足"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4060500,
|
||||||
|
"description": "用户配额已用光"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4070000,
|
||||||
|
"description": "用户上传文件异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4070100,
|
||||||
|
"description": "用户上传文件类型不匹配"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4070200,
|
||||||
|
"description": "用户上传文件太大"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4070300,
|
||||||
|
"description": "用户上传图片太大"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4070400,
|
||||||
|
"description": "用户上传视频太大"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4070500,
|
||||||
|
"description": "用户上传压缩文件太大"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4080000,
|
||||||
|
"description": "用户当前版本异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4080100,
|
||||||
|
"description": "用户安装版本与系统不匹配",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4080101,
|
||||||
|
"description": "用户安装版本过低"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4080102,
|
||||||
|
"description": "用户安装版本过高"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4080103,
|
||||||
|
"description": "用户安装版本已过期"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4080200,
|
||||||
|
"description": "用户 API 请求版本不匹配",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4080201,
|
||||||
|
"description": "用户 API 请求版本过高"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4080202,
|
||||||
|
"description": "用户 API 请求版本过低"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090000,
|
||||||
|
"description": "用户隐私未授权",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4090100,
|
||||||
|
"description": "用户隐私未签署"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090200,
|
||||||
|
"description": "用户摄像头未授权"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090300,
|
||||||
|
"description": "用户相机未授权"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090400,
|
||||||
|
"description": "用户图片库未授权"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090500,
|
||||||
|
"description": "用户文件未授权"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090600,
|
||||||
|
"description": "用户位置信息未授权"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4090700,
|
||||||
|
"description": "用户通讯录未授权"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4100000,
|
||||||
|
"description": "用户设备异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4100100,
|
||||||
|
"description": "用户相机异常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4100200,
|
||||||
|
"description": "用户麦克风异常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4100300,
|
||||||
|
"description": "用户听筒异常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4100400,
|
||||||
|
"description": "用户扬声器异常"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4100500,
|
||||||
|
"description": "用户 GPS 定位异常"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4110000,
|
||||||
|
"description": "数据资源异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 4110100,
|
||||||
|
"description": "数据资源不存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 4110200,
|
||||||
|
"description": "数据操作的行数不符合预期"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5000000,
|
||||||
|
"description": "系统执行出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5010000,
|
||||||
|
"description": "系统执行超时",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5010100,
|
||||||
|
"description": "系统订单处理超时"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5020000,
|
||||||
|
"description": "系统容灾功能被触发",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5020100,
|
||||||
|
"description": "系统限流"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5020200,
|
||||||
|
"description": "系统功能降级"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030000,
|
||||||
|
"description": "系统资源异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5030100,
|
||||||
|
"description": "系统资源耗尽",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5030101,
|
||||||
|
"description": "系统磁盘空间耗尽"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030102,
|
||||||
|
"description": "系统内存耗尽"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030103,
|
||||||
|
"description": "文件句柄耗尽"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030104,
|
||||||
|
"description": "系统连接池耗尽"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030105,
|
||||||
|
"description": "系统线程池耗尽"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 5030200,
|
||||||
|
"description": "系统资源访问异常",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 5030201,
|
||||||
|
"description": "系统读取磁盘文件失败"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6000000,
|
||||||
|
"description": "调用第三方服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010000,
|
||||||
|
"description": "中间件服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010100,
|
||||||
|
"description": "RPC 服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010101,
|
||||||
|
"description": "RPC 服务未找到"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010102,
|
||||||
|
"description": "RPC 服务未注册"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010103,
|
||||||
|
"description": "接口不存在"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010200,
|
||||||
|
"description": "消息服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010201,
|
||||||
|
"description": "消息投递出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010202,
|
||||||
|
"description": "消息消费出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010203,
|
||||||
|
"description": "消息订阅出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010204,
|
||||||
|
"description": "消息分组未查到"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010300,
|
||||||
|
"description": "缓存服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010301,
|
||||||
|
"description": "key 长度超过限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010302,
|
||||||
|
"description": "value 长度超过限制"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010303,
|
||||||
|
"description": "存储容量已满"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010304,
|
||||||
|
"description": "不支持的数据格式"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010400,
|
||||||
|
"description": "配置服务出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010500,
|
||||||
|
"description": "网络资源服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6010501,
|
||||||
|
"description": "VPN 服务出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010502,
|
||||||
|
"description": "CDN 服务出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010503,
|
||||||
|
"description": "域名解析服务出错"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6010504,
|
||||||
|
"description": "网关服务出错"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6020000,
|
||||||
|
"description": "第三方系统执行超时",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6020100,
|
||||||
|
"description": "RPC 执行超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6020200,
|
||||||
|
"description": "消息投递超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6020300,
|
||||||
|
"description": "缓存服务超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6020400,
|
||||||
|
"description": "配置服务超时"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6020500,
|
||||||
|
"description": "数据库服务超时"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6030000,
|
||||||
|
"description": "数据库服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6030100,
|
||||||
|
"description": "表不存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6030200,
|
||||||
|
"description": "列不存在"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6030300,
|
||||||
|
"description": "多表关联中存在多个相同名称的列"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6030400,
|
||||||
|
"description": "数据库死锁"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6030500,
|
||||||
|
"description": "主键冲突"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6040000,
|
||||||
|
"description": "第三方容灾系统被触发",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6040100,
|
||||||
|
"description": "第三方系统限流"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6040200,
|
||||||
|
"description": "第三方功能降级"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6050000,
|
||||||
|
"description": "通知服务出错",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"code": 6050100,
|
||||||
|
"description": "短信提醒服务失败"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6050200,
|
||||||
|
"description": "语音提醒服务失败"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6050300,
|
||||||
|
"description": "邮件提醒服务失败"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>plusone-basic</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>plusone-basic-application</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-domain</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 防止XSS攻击的过滤插件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.dreamlu</groupId>
|
||||||
|
<artifactId>mica-xss</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.exception.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler.ExceptionInfoHolder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class PlusoneExceptionHandlerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
ExceptionInfoHolder exceptionInfoHolder() {
|
||||||
|
return new ExceptionInfoHolder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package xyz.zhouxy.plusone.exception.handler;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有异常的处理器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@ConditionalOnProperty(prefix = "plusone.exception", name = "handle-all-exception", havingValue = "true")
|
||||||
|
@RestControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class AllExceptionHandler extends BaseExceptionHandler {
|
||||||
|
protected AllExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||||
|
super(exceptionInfoHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<RestfulResult> handleException(Throwable e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return this.buildExceptionResponse(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package xyz.zhouxy.plusone.exception.handler;
|
||||||
|
|
||||||
|
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认异常的处理器
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 对 {@link IllegalArgumentException} 异常做了写默认处理。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 将异常信息返回给前端时,隐藏掉数据库异常信息中关于数据库结构、SQL 语句的描述。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 为了避免因为 {@link AllExceptionHandler} 而失效,
|
||||||
|
* {@code Order} 设置为了 {@value Ordered.LOWEST_PRECEDENCE - 1}。
|
||||||
|
* 一般 Spring Bean 的 Order 应该是 Ordered.LOWEST_PRECEDENCE,
|
||||||
|
* 所以如果想让自定义的异常处理器与本处理器有冲突,希望覆盖本处理器的行为,
|
||||||
|
* 需要添加 {@link Order} 注解,将 order 修改为比 {@code Ordered.LOWEST_PRECEDENCE - 1}
|
||||||
|
* 更小的值,
|
||||||
|
* 如 0 或者 1,甚至是负数。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
@Order(Ordered.LOWEST_PRECEDENCE - 1)
|
||||||
|
@Slf4j
|
||||||
|
public class DefaultExceptionHandler extends BaseExceptionHandler {
|
||||||
|
|
||||||
|
public DefaultExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||||
|
super(exceptionInfoHolder);
|
||||||
|
set(IllegalArgumentException.class, 4010000, "格式错误", HttpStatus.FORBIDDEN);
|
||||||
|
set(DataAccessException.class, 6030000, "数据库错误", HttpStatus.INTERNAL_SERVER_ERROR, true);
|
||||||
|
set(MethodArgumentNotValidException.class,
|
||||||
|
4040401,
|
||||||
|
e -> ((MethodArgumentNotValidException) e).getAllErrors()
|
||||||
|
.stream()
|
||||||
|
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||||
|
.toList()
|
||||||
|
.toString(),
|
||||||
|
HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<RestfulResult> handleException(Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return buildExceptionResponse(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>plusone-basic</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>plusone-basic-common</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-tx</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy.plusone</groupId>
|
||||||
|
<artifactId>plusone-commons</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy.plusone</groupId>
|
||||||
|
<artifactId>plusone-exception-handler</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.zhouxy.plusone.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要时,当查询数据不存在时抛出的异常
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 暂时先这样,后续完善异常体系时可能会更改。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see xyz.zhouxy.plusone.util.AssertResult
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public class DataNotExistException extends PlusoneException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = 6536955800679703111L;
|
||||||
|
|
||||||
|
public static final int ERROR_CODE = 4110100;
|
||||||
|
|
||||||
|
public DataNotExistException() {
|
||||||
|
super(ERROR_CODE, "数据不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataNotExistException(String message) {
|
||||||
|
super(ERROR_CODE, message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.zhouxy.plusone.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要时,当数据操作的行数不符合预期时抛出的异常
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 暂时先这样,后续完善异常体系时可能会更改。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see xyz.zhouxy.plusone.util.AssertResult
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public class DataOperationNumberException extends PlusoneException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = -9220765735990318186L;
|
||||||
|
|
||||||
|
public static final int ERROR_CODE = 4110200;
|
||||||
|
|
||||||
|
public DataOperationNumberException() {
|
||||||
|
super(ERROR_CODE, "数据操作的行数不符合预期");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataOperationNumberException(String message) {
|
||||||
|
super(ERROR_CODE, message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package xyz.zhouxy.plusone.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4040200 - 无效的用户输入
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class InvalidInputException extends PlusoneException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = 7956661913360059670L;
|
||||||
|
|
||||||
|
public static final int ERROR_CODE = 4040200;
|
||||||
|
|
||||||
|
private InvalidInputException(int code, String msg) {
|
||||||
|
super(code, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InvalidInputException(int code, Throwable cause) {
|
||||||
|
super(code, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InvalidInputException(int code, String msg, Throwable cause) {
|
||||||
|
super(code, msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidInputException(String msg) {
|
||||||
|
this(ERROR_CODE, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidInputException(Throwable cause) {
|
||||||
|
this(ERROR_CODE, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidInputException(String msg, Throwable cause) {
|
||||||
|
this(ERROR_CODE, msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不支持的 Principal 类型出现时抛出的异常
|
||||||
|
*/
|
||||||
|
public static InvalidInputException unsupportedPrincipalTypeException() {
|
||||||
|
return unsupportedPrincipalTypeException("不支持的 PrincipalType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不支持的 Principal 类型出现时抛出的异常
|
||||||
|
*/
|
||||||
|
public static InvalidInputException unsupportedPrincipalTypeException(String message) {
|
||||||
|
return new InvalidInputException(4040201, message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package xyz.zhouxy.plusone.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4040600 - 用户操作异常
|
||||||
|
*
|
||||||
|
* @author ZhouXY
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class UserOperationException extends PlusoneException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = 4371055414421991940L;
|
||||||
|
|
||||||
|
public static final int DEFAULT_ERROR_CODE = 4040600;
|
||||||
|
|
||||||
|
public UserOperationException(String msg) {
|
||||||
|
super(DEFAULT_ERROR_CODE, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOperationException(String msg, Throwable cause) {
|
||||||
|
super(DEFAULT_ERROR_CODE, msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOperationException(int code, String msg) {
|
||||||
|
super(code, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOperationException(int code, Throwable cause) {
|
||||||
|
super(code, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserOperationException(int code, String msg, Throwable cause) {
|
||||||
|
super(code, msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无效的操作
|
||||||
|
*
|
||||||
|
* @return 异常对象
|
||||||
|
*/
|
||||||
|
public static UserOperationException invalidOperation() {
|
||||||
|
return invalidOperation("无效的操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无效的操作
|
||||||
|
*
|
||||||
|
* @param msg 异常信息
|
||||||
|
* @return 异常对象
|
||||||
|
*/
|
||||||
|
public static UserOperationException invalidOperation(String msg) {
|
||||||
|
return new UserOperationException(4040604, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package xyz.zhouxy.plusone.util;
|
||||||
|
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.DataNotExistException;
|
||||||
|
import xyz.zhouxy.plusone.exception.DataOperationNumberException;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对数据库执行结果进行判断
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public final class AssertResult {
|
||||||
|
|
||||||
|
private AssertResult() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(boolean expression) {
|
||||||
|
if (!expression) {
|
||||||
|
throw new DataOperationNumberException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(boolean expression, String message) {
|
||||||
|
if (!expression) {
|
||||||
|
throw new DataOperationNumberException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(Object i, int expectedValue) {
|
||||||
|
if (!Objects.equals(i, expectedValue)) {
|
||||||
|
throw new DataOperationNumberException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(Object i, int expectedValue, String format) {
|
||||||
|
if (!Objects.equals(i, expectedValue)) {
|
||||||
|
throw new DataOperationNumberException(String.format(format, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exist(boolean expression) {
|
||||||
|
if (!expression) {
|
||||||
|
throw new DataNotExistException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exist(boolean expression, String message) {
|
||||||
|
if (!expression) {
|
||||||
|
throw new DataNotExistException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nonNull(Object obj) {
|
||||||
|
if (Objects.isNull(obj)) {
|
||||||
|
throw new DataNotExistException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nonNull(Object obj, String message) {
|
||||||
|
if (Objects.isNull(obj)) {
|
||||||
|
throw new DataNotExistException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package xyz.zhouxy.plusone.util;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串工具类
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public class PlusoneStrUtil {
|
||||||
|
|
||||||
|
private PlusoneStrUtil() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String EMPTY_STR = "";
|
||||||
|
|
||||||
|
public static String getTextOrEmpty(String value) {
|
||||||
|
return StringUtils.hasText(value) ? value : EMPTY_STR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStrOrEmpty(String value) {
|
||||||
|
return StringUtils.hasLength(value) ? value : EMPTY_STR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String checkString(String value, String regex, String message) {
|
||||||
|
Assert.notNull(value, message);
|
||||||
|
Assert.isTrue(Pattern.matches(regex, value), message);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String checkStringNullable(String value, String regex, String message) {
|
||||||
|
return Objects.nonNull(value) ? checkString(value, regex, message) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String checkStringOrDefault(String value, String regex, String message) {
|
||||||
|
return StringUtils.hasText(value) ? checkString(value, regex, message) : "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>plusone-basic-domain</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<artifactId>plusone-basic-common</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<version>2.13.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>2.13.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,32 @@
|
||||||
|
package xyz.zhouxy.plusone.constant;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.util.Enumeration;
|
||||||
|
import xyz.zhouxy.plusone.util.EnumerationValuesHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体状态
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public class EntityStatus extends Enumeration<EntityStatus> {
|
||||||
|
|
||||||
|
private EntityStatus(int value, String name) {
|
||||||
|
super(value, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 常量
|
||||||
|
public static final EntityStatus AVAILABLE = new EntityStatus(0, "正常");
|
||||||
|
public static final EntityStatus DISABLED = new EntityStatus(1, "禁用");
|
||||||
|
|
||||||
|
private static final EnumerationValuesHolder<EntityStatus> ENUMERATION_VALUES = new EnumerationValuesHolder<>(
|
||||||
|
new EntityStatus[] { AVAILABLE, DISABLED });
|
||||||
|
|
||||||
|
public static EntityStatus of(int value) {
|
||||||
|
return ENUMERATION_VALUES.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EntityStatus" + super.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package xyz.zhouxy.plusone.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正则表达式常量
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public final class RegexConsts {
|
||||||
|
|
||||||
|
public static final String DATE = "^\\d{4}-\\d{2}-\\d{2}";
|
||||||
|
|
||||||
|
public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[\\w\\\\!#$%&'*\\+\\-/=?^`{|}~@\\(\\)\\[\\]\",\\.;':><]{8,32}$";
|
||||||
|
|
||||||
|
public static final String CAPTCHA = "^[0-9A-Za-z]{4,6}$";
|
||||||
|
|
||||||
|
public static final String EMAIL = "^\\w+([-+.]\\w+)*@[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})*(\\.(?![0-9]+$)[a-zA-Z0-9][-0-9A-Za-z]{0,62})$";
|
||||||
|
|
||||||
|
public static final String MOBILE_PHONE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$";
|
||||||
|
|
||||||
|
public static final String USERNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$";
|
||||||
|
|
||||||
|
public static final String NICKNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$";
|
||||||
|
|
||||||
|
private RegexConsts() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聚合根
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 由 Repository 负责整个聚合根的查询、保存、删除。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see Entity
|
||||||
|
* @see IRepository
|
||||||
|
*/
|
||||||
|
public abstract class AggregateRoot<ID extends Serializable> extends Entity<ID> {
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领域事件
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>根据所使用的消息机制的不同,可能需要继承其它类,或实现 {@link java.io.Serializable} 接口</b>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see Entity
|
||||||
|
* @see IEventHandler
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class DomainEvent {
|
||||||
|
private String identifier;
|
||||||
|
private long happenedAt;
|
||||||
|
|
||||||
|
protected DomainEvent() {
|
||||||
|
this.identifier = UUID.randomUUID().toString(true);
|
||||||
|
this.happenedAt = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* DDD 中的实体,带有 ID。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 维护一个 {@link DomainEvent} 的列表,持久化时可将其中的领域事件进行发布。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see IValueObject
|
||||||
|
* @see AggregateRoot
|
||||||
|
*/
|
||||||
|
public abstract class Entity<ID extends Serializable> {
|
||||||
|
|
||||||
|
private final List<DomainEvent> domainEvents = new ArrayList<>();
|
||||||
|
|
||||||
|
public abstract Optional<ID> getId();
|
||||||
|
|
||||||
|
protected void addDomainEvent(DomainEvent domainEvent) {
|
||||||
|
domainEvents.add(domainEvent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see ICommandHandler
|
||||||
|
*/
|
||||||
|
public interface ICommand {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令处理器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see ICommand
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ICommandHandler<T extends ICommand> {
|
||||||
|
void handle(T command);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件处理器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see DomainEvent
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface IEventHandler<E extends DomainEvent> {
|
||||||
|
void handle(E command);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository 基础接口
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see AggregateRoot
|
||||||
|
*/
|
||||||
|
public interface IRepository<T extends AggregateRoot<ID>, ID extends Serializable> {
|
||||||
|
|
||||||
|
T find(ID id);
|
||||||
|
|
||||||
|
T save(T entity);
|
||||||
|
|
||||||
|
void delete(T entity);
|
||||||
|
|
||||||
|
boolean exists(ID id);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值对象
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface IValueObject {
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带可读的 label 属性
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface IWithLabel {
|
||||||
|
String getLabel();
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带 orderNumber 字段,可用来排序
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface IWithOrderNumber extends Comparable<IWithOrderNumber> {
|
||||||
|
|
||||||
|
int getOrderNumber();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(IWithOrderNumber that) {
|
||||||
|
return new OrderNumberComparator<IWithOrderNumber>().compare(this, that);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderNumberComparator<T extends Comparable<T>>
|
||||||
|
implements Comparator<IWithOrderNumber> {
|
||||||
|
@Override
|
||||||
|
public int compare(IWithOrderNumber a, IWithOrderNumber b) {
|
||||||
|
if (Objects.equals(a, b)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (b == null) ? 1 : Integer.compare(a.getOrderNumber(), b.getOrderNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带版本号
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface IWithVersion {
|
||||||
|
long getVersion();
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package xyz.zhouxy.plusone.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带校验的字符串值对象
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public abstract class ValidatableStringRecord implements IValueObject {
|
||||||
|
|
||||||
|
protected String value;
|
||||||
|
protected final String format;
|
||||||
|
|
||||||
|
protected ValidatableStringRecord(String format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
protected boolean isValid() {
|
||||||
|
return value.matches(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public String value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package xyz.zhouxy.plusone.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class RegexUtil {
|
||||||
|
|
||||||
|
private RegexUtil() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matches(CharSequence input, String regex) {
|
||||||
|
return Pattern.matches(regex, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesOr(CharSequence input, String... regexs) {
|
||||||
|
boolean isMatched;
|
||||||
|
for (var regex : regexs) {
|
||||||
|
isMatched = Pattern.matches(regex, input);
|
||||||
|
if (isMatched) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesAnd(CharSequence input, String... regexs) {
|
||||||
|
boolean isMatched;
|
||||||
|
for (var regex : regexs) {
|
||||||
|
isMatched = Pattern.matches(regex, input);
|
||||||
|
if (!isMatched) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-domain</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tencentcloudapi</groupId>
|
||||||
|
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,14 @@
|
||||||
|
package xyz.zhouxy.plusone.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plusone 配置。加载 plusone.properties 文件
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@PropertySource(value = {"classpath:conf/plusone.properties"}, encoding = "utf-8")
|
||||||
|
public class PlusoneConfig {
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package xyz.zhouxy.plusone.infrastructure.pojo;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体的持久化对象
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <i>由于本项目目前是手动实现 Repository,直接将实体持久化,
|
||||||
|
* 查询结果也是直接实例化为 Entity,故暂时不使用 PO。</i>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public abstract class AbstractEntityPO {
|
||||||
|
protected Long createdBy;
|
||||||
|
protected LocalDateTime createTime;
|
||||||
|
protected Long updatedBy;
|
||||||
|
protected LocalDateTime updateTime;
|
||||||
|
|
||||||
|
public abstract Long getId();
|
||||||
|
|
||||||
|
public void auditFields(Long updatedBy, LocalDateTime updateTime) {
|
||||||
|
this.updatedBy = updatedBy;
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void auditFields(Long createdBy, LocalDateTime createTime, Long updatedBy, LocalDateTime updateTime) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
this.createTime = createTime;
|
||||||
|
this.updatedBy = updatedBy;
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展了 {@link BeanPropertySqlParameterSource},在将 POJO 转换为
|
||||||
|
* {@link org.springframework.jdbc.core.namedparam.SqlParameterSource} 时,
|
||||||
|
* 使用 {@link Enum#ordinal()} 将枚举转换成整数。
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*
|
||||||
|
* @see SqlParameterSource
|
||||||
|
* @see BeanPropertySqlParameterSource
|
||||||
|
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
|
* @see Enum
|
||||||
|
*/
|
||||||
|
public class BeanPropertyParamSource extends BeanPropertySqlParameterSource {
|
||||||
|
|
||||||
|
public BeanPropertyParamSource(Object object) {
|
||||||
|
super(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object getValue(String paramName) throws IllegalArgumentException {
|
||||||
|
Object value = super.getValue(paramName);
|
||||||
|
if (value instanceof Enum) {
|
||||||
|
return ((Enum<?>) value).ordinal();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
|
||||||
|
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||||
|
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
|
||||||
|
import org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.jdbc.converter.EnumToOrdinalConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDBC 配置
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 配置了类型转换器
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 这里声明了 {@link DefaultQueryMappingConfiguration} 的实例为 Spring bean。
|
||||||
|
* 在其它配置类中注入该 bean,就可以向其中添加
|
||||||
|
* {@link org.springframework.jdbc.core.RowMapper}
|
||||||
|
* 供 Spring Data JDBC 查询时使用。如下所示:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code @Configuration}
|
||||||
|
* class CustomConfig {
|
||||||
|
* public CustomConfig(DefaultQueryMappingConfiguration rowMappers) {
|
||||||
|
* rowMappers.registerRowMapper(Person.class, new PersonRowMapper());
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class JdbcConfig extends AbstractJdbcConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JdbcCustomConversions jdbcCustomConversions() {
|
||||||
|
return new JdbcCustomConversions(List.of(EnumToOrdinalConverter.INSTANCE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
QueryMappingConfiguration rowMappers() {
|
||||||
|
return new DefaultQueryMappingConfiguration();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.domain.Entity;
|
||||||
|
|
||||||
|
public abstract class JdbcEntityDaoSupport<T extends Entity<ID>, ID extends Serializable> {
|
||||||
|
protected final NamedParameterJdbcTemplate jdbc;
|
||||||
|
|
||||||
|
protected RowMapper<T> rowMapper;
|
||||||
|
protected ResultSetExtractor<T> resultSetExtractor;
|
||||||
|
|
||||||
|
protected JdbcEntityDaoSupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||||
|
this.jdbc = namedParameterJdbcTemplate;
|
||||||
|
this.rowMapper = (ResultSet rs, int rowNum) -> mapRow(rs);
|
||||||
|
this.resultSetExtractor = (ResultSet rs) -> rs.next() ? mapRow(rs) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final T queryForObject(String sql) {
|
||||||
|
return this.jdbc.query(sql, this.resultSetExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final T queryForObject(String sql, SqlParameterSource paramSource) {
|
||||||
|
return this.jdbc.query(sql, paramSource, this.resultSetExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final List<T> queryForList(String sql) {
|
||||||
|
return this.jdbc.query(sql, this.rowMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final List<T> queryForList(String sql, SqlParameterSource parameterSource) {
|
||||||
|
return this.jdbc.query(sql, parameterSource, this.rowMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Stream<T> queryForStream(String sql, SqlParameterSource parameterSource) {
|
||||||
|
return this.jdbc.queryForStream(sql, parameterSource, this.rowMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final <E> Stream<E> queryForStream(String sql, SqlParameterSource parameterSource, Class<E> elementType) {
|
||||||
|
return this.jdbc.queryForList(sql, parameterSource, elementType).stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean queryExists(String sql, SqlParameterSource parameterSource) {
|
||||||
|
Boolean isExists = this.jdbc.query(sql, parameterSource, ResultSet::next);
|
||||||
|
return Boolean.TRUE.equals(isExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T mapRow(ResultSet rs) throws SQLException;
|
||||||
|
|
||||||
|
protected void setRowMapper(@Nonnull RowMapper<T> rowMapper) {
|
||||||
|
this.rowMapper = rowMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setResultSetExtractor(@Nonnull ResultSetExtractor<T> resultSetExtractor) {
|
||||||
|
this.resultSetExtractor = resultSetExtractor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.spring.SpringContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局单例,由 Spring 装配好的对象。
|
||||||
|
* 可通过静态方法获取 Spring 容器中的 {@link JdbcTemplate} 和
|
||||||
|
* {@link NamedParameterJdbcTemplate} 对象。
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see JdbcTemplate
|
||||||
|
* @see NamedParameterJdbcTemplate
|
||||||
|
*/
|
||||||
|
public final class JdbcFactory {
|
||||||
|
|
||||||
|
private JdbcFactory() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JdbcTemplate getJdbcTemplate() {
|
||||||
|
return SpringContextHolder.getContext().getBean(JdbcTemplate.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
|
||||||
|
return SpringContextHolder.getContext().getBean(NamedParameterJdbcTemplate.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.domain.AggregateRoot;
|
||||||
|
import xyz.zhouxy.plusone.domain.IRepository;
|
||||||
|
|
||||||
|
public abstract class JdbcRepositorySupport<T extends AggregateRoot<ID>, ID extends Serializable>
|
||||||
|
extends JdbcEntityDaoSupport<T, ID>
|
||||||
|
implements IRepository<T, ID> {
|
||||||
|
|
||||||
|
protected JdbcRepositorySupport(@Nonnull NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
|
||||||
|
super(namedParameterJdbcTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doDelete(@Nonnull T entity);
|
||||||
|
|
||||||
|
protected abstract T doFindById(@Nonnull ID id);
|
||||||
|
|
||||||
|
protected abstract T doInsert(@Nonnull T entity);
|
||||||
|
|
||||||
|
protected abstract T doUpdate(@Nonnull T entity);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void delete(T entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot delete null.");
|
||||||
|
}
|
||||||
|
doDelete(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final T find(ID id) {
|
||||||
|
if (id == null) {
|
||||||
|
throw new IllegalArgumentException("Id cannot be null.");
|
||||||
|
}
|
||||||
|
return doFindById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final T save(T entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot save null.");
|
||||||
|
}
|
||||||
|
return entity.getId().isPresent() ? doUpdate(entity) : doInsert(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract SqlParameterSource generateParamSource(ID id, @Nonnull T entity);
|
||||||
|
|
||||||
|
protected final SqlParameterSource generateParamSource(@Nonnull T entity) {
|
||||||
|
return generateParamSource(entity.getId().orElseThrow(), entity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc.common;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询结果处理
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 通过在 {@link #map(ResultSet)} 中配置 {@link ResultSet} 到对象的映射,
|
||||||
|
* 可将 {@link #rowMapper(ResultSet, int)} 的方法应用,
|
||||||
|
* 直接当成 {@link org.springframework.jdbc.core.RowMapper} 对象传给
|
||||||
|
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
|
||||||
|
* 的查询方法,
|
||||||
|
* 或者在 Spring Data JDBC 的配置中使用。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 在
|
||||||
|
* {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate}
|
||||||
|
* 的 query(String, java.util.Map, ResultSetExtractor)
|
||||||
|
* 和 query(String, SqlParameterSource, ResultSetExtractor)
|
||||||
|
* 两个方法执行时,ResultSetExtractor 中需要执行一次 {@link ResultSet#next()} 判断是否查询到数据,
|
||||||
|
* 如果查询到数据再执行查询结果的实例化。
|
||||||
|
* {@link #resultSetExtractor(ResultSet)} 封装了这个过程,
|
||||||
|
* 和 {@link #rowMapper(ResultSet, int)}一样,
|
||||||
|
* 直接把方法引用当成 {@link org.springframework.jdbc.core.ResultSetExtractor} 的对象即可。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see org.springframework.data.jdbc.repository.config.DefaultQueryMappingConfiguration
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ResultMapper<T> {
|
||||||
|
|
||||||
|
T map(ResultSet resultSet) throws SQLException;
|
||||||
|
|
||||||
|
default T rowMapper(ResultSet resultSet, int rowNum) throws SQLException {
|
||||||
|
return map(resultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
default T resultSetExtractor(ResultSet resultSet) throws SQLException {
|
||||||
|
return resultSet.next() ? map(resultSet) : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc.common;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
|
||||||
|
public class SimpleResultMapper<T> implements ResultMapper<T> {
|
||||||
|
|
||||||
|
private final RowMapper<T> rowMapper;
|
||||||
|
|
||||||
|
public SimpleResultMapper(RowMapper<T> rowMapper) {
|
||||||
|
this.rowMapper = rowMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T map(ResultSet resultSet) throws SQLException {
|
||||||
|
return rowMapper.mapRow(resultSet, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T rowMapper(ResultSet resultSet, int rowNum) throws SQLException {
|
||||||
|
return this.rowMapper.mapRow(resultSet, rowNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> SimpleResultMapper<T> of(Class<T> clazz) {
|
||||||
|
return new SimpleResultMapper<>(new BeanPropertyRowMapper<>(clazz));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc.converter;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.data.convert.WritingConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举序号转换器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@WritingConverter
|
||||||
|
public enum EnumToOrdinalConverter implements Converter<Enum<?>, Integer> {
|
||||||
|
INSTANCE
|
||||||
|
;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer convert(Enum<?> source) {
|
||||||
|
return source.ordinal();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package xyz.zhouxy.plusone.jdbc.converter;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.data.convert.ReadingConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序号枚举转换器
|
||||||
|
* <p>
|
||||||
|
* 向 {@link org.springframework.data.jdbc.core.convert.JdbcCustomConversions}
|
||||||
|
* 中添加类型转换器,
|
||||||
|
* 如:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* return new JdbcCustomConversions(List.of(new OrdinalToEnumConverter<SystemStatus>()));
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*
|
||||||
|
* @see Converter
|
||||||
|
* @see org.springframework.data.jdbc.core.convert.JdbcCustomConversions
|
||||||
|
*/
|
||||||
|
@ReadingConverter
|
||||||
|
public class OrdinalToEnumConverter<E extends Enum<E>> implements Converter<Integer, Enum<E>> {
|
||||||
|
|
||||||
|
private final Class<E> type;
|
||||||
|
private final E[] constants;
|
||||||
|
|
||||||
|
public OrdinalToEnumConverter(Class<E> type) {
|
||||||
|
this.type = type;
|
||||||
|
this.constants = type.getEnumConstants();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enum<E> convert(Integer ordinal) {
|
||||||
|
try {
|
||||||
|
return constants[ordinal];
|
||||||
|
} catch (ArrayIndexOutOfBoundsException exception) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Cannot convert %d to %s by ordinal value.", ordinal, type.getSimpleName()),
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.zhouxy.plusone.mail;
|
||||||
|
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 {@link SimpleMailMessage}
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public class MailMessageFactory {
|
||||||
|
private final PlusoneMailProperties mailProperties;
|
||||||
|
|
||||||
|
public MailMessageFactory(PlusoneMailProperties mailProperties) {
|
||||||
|
this.mailProperties = mailProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleMailMessage getCodeMailMessage(String code, String to) {
|
||||||
|
String subject = mailProperties.getSubject().get("code");
|
||||||
|
String content = String.format(mailProperties.getTemplate().get("code"), code);
|
||||||
|
return getSimpleMailMessage(to, subject, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleMailMessage getSimpleMailMessage(String to, String subject, String content) {
|
||||||
|
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
|
||||||
|
simpleMailMessage.setFrom(mailProperties.getFrom());
|
||||||
|
simpleMailMessage.setTo(to);
|
||||||
|
simpleMailMessage.setSubject(subject);
|
||||||
|
simpleMailMessage.setText(content);
|
||||||
|
return simpleMailMessage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.mail;
|
||||||
|
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件服务
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface MailService {
|
||||||
|
|
||||||
|
void sendSimpleMail(String to, String subject, String content);
|
||||||
|
|
||||||
|
void sendHtmlMail(String to, String subject, String content) throws MessagingException;
|
||||||
|
|
||||||
|
void sendCodeMail(String code, String to);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package xyz.zhouxy.plusone.mail;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取配置文件,实例化 {@link MailService} 对象后,放到 Spring 容器中。
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(PlusoneMailProperties.class)
|
||||||
|
@ConditionalOnClass(MailService.class)
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
public class PlusoneMailAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MailService mailService(JavaMailSender mailSender, PlusoneMailProperties mailProperties) {
|
||||||
|
MailMessageFactory mailMessageFactory = new MailMessageFactory(mailProperties);
|
||||||
|
return new SimpleMailService(mailSender, mailMessageFactory);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package xyz.zhouxy.plusone.mail;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ConfigurationProperties("plusone.mail")
|
||||||
|
public class PlusoneMailProperties {
|
||||||
|
private String from;
|
||||||
|
private Map<String, String> subject;
|
||||||
|
private Map<String, String> template;
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package xyz.zhouxy.plusone.mail;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.mail.MailException;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件服务。<b>不能发送 HTML 邮件。</b>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SimpleMailService implements MailService {
|
||||||
|
|
||||||
|
private final JavaMailSender mailSender;
|
||||||
|
private final MailMessageFactory mailMessageFactory;
|
||||||
|
|
||||||
|
public SimpleMailService(JavaMailSender mailSender, MailMessageFactory mailMessageFactory) {
|
||||||
|
this.mailSender = mailSender;
|
||||||
|
this.mailMessageFactory = mailMessageFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本邮件
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendSimpleMail(String to, String subject, String content) {
|
||||||
|
SimpleMailMessage message = mailMessageFactory.getSimpleMailMessage(to, subject, content);
|
||||||
|
try {
|
||||||
|
mailSender.send(message);
|
||||||
|
log.debug("简单邮件已经发送。");
|
||||||
|
} catch (MailException e) {
|
||||||
|
log.error("发送简单邮件时发生异常!", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendHtmlMail(String to, String subject, String content) throws MessagingException {
|
||||||
|
throw new UnsupportedOperationException("暂不支持");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendCodeMail(String code, String to) {
|
||||||
|
SimpleMailMessage message = mailMessageFactory.getCodeMailMessage(code, to);
|
||||||
|
try {
|
||||||
|
mailSender.send(message);
|
||||||
|
log.debug("简单邮件已经发送。");
|
||||||
|
} catch (MailException e) {
|
||||||
|
log.error("发送简单邮件时发生异常!", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.mybatis;
|
||||||
|
|
||||||
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
public class MyBatisAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MybatisUtil mybatisUtil(SqlSessionFactory sqlSessionFactory) {
|
||||||
|
return MybatisUtil.getInstance()
|
||||||
|
.setSqlSessionFactory(sqlSessionFactory);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package xyz.zhouxy.plusone.mybatis;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class MyBatisPlusConfig {
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
|
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||||
|
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package xyz.zhouxy.plusone.mybatis;
|
||||||
|
|
||||||
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
|
||||||
|
public final class MybatisUtil {
|
||||||
|
|
||||||
|
private SqlSessionFactory sqlSessionFactory;
|
||||||
|
|
||||||
|
private MybatisUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
MybatisUtil setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
|
||||||
|
this.sqlSessionFactory = sqlSessionFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Holder {
|
||||||
|
private static final MybatisUtil INSTANCE = new MybatisUtil();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MybatisUtil getInstance() {
|
||||||
|
return Holder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlSessionFactory getSqlSessionFactory() {
|
||||||
|
return MybatisUtil.getInstance().sqlSessionFactory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package xyz.zhouxy.plusone.redis;
|
||||||
|
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 Redis 存放临时字符串
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class RedisStrCacheDAO implements StrCacheDAO {
|
||||||
|
|
||||||
|
private final StringRedisTemplate template;
|
||||||
|
|
||||||
|
public RedisStrCacheDAO(StringRedisTemplate template) {
|
||||||
|
this.template = template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(String key, String value, long timeout, TimeUnit unit) {
|
||||||
|
ValueOperations<String, String> ops = template.opsForValue();
|
||||||
|
ops.set(key, value, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(String key) {
|
||||||
|
ValueOperations<String, String> ops = this.template.opsForValue();
|
||||||
|
return ops.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValueAndDelete(String key) {
|
||||||
|
ValueOperations<String, String> ops = this.template.opsForValue();
|
||||||
|
String value = ops.get(key);
|
||||||
|
template.delete(key);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.redis;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串缓存接口
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface StrCacheDAO {
|
||||||
|
|
||||||
|
void setValue(String key, String value, long timeout, TimeUnit unit);
|
||||||
|
|
||||||
|
String getValue(String key);
|
||||||
|
|
||||||
|
String getValueAndDelete(String key);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package xyz.zhouxy.plusone.sms;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS 相关配置
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(value = {
|
||||||
|
SmsProperties.class,
|
||||||
|
SmsCredentialProperties.class,
|
||||||
|
SmsClientProperties.class,
|
||||||
|
SmsHttpProperties.class,
|
||||||
|
SmsProxyProperties.class})
|
||||||
|
@ConditionalOnClass(SmsService.class)
|
||||||
|
public class PlusoneSmsAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SmsService smsService(SmsProperties smsProperties) {
|
||||||
|
return new TencentSmsServiceImpl(smsProperties);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package xyz.zhouxy.plusone.sms;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS 相关参数
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("plusone.sms")
|
||||||
|
public class SmsProperties {
|
||||||
|
private String region;
|
||||||
|
private SmsCredentialProperties credential;
|
||||||
|
private SmsClientProperties client;
|
||||||
|
private String appId;
|
||||||
|
private Map<String, String> templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("plusone.sms.credential")
|
||||||
|
class SmsCredentialProperties {
|
||||||
|
private String secretId;
|
||||||
|
private String secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("plusone.sms.client")
|
||||||
|
class SmsClientProperties {
|
||||||
|
private String signMethod;
|
||||||
|
private SmsHttpProperties http;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("plusone.sms.client.http")
|
||||||
|
class SmsHttpProperties {
|
||||||
|
private SmsProxyProperties proxy;
|
||||||
|
private String reqMethod;
|
||||||
|
private Integer connTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("plusone.sms.client.http.proxy")
|
||||||
|
class SmsProxyProperties {
|
||||||
|
private String host;
|
||||||
|
private Integer port;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package xyz.zhouxy.plusone.sms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS 服务
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface SmsService {
|
||||||
|
|
||||||
|
void sendCodeMessage(String code, String phoneNumber);
|
||||||
|
|
||||||
|
void sendCodeMessage(String signName, String code, String phoneNumber);
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package xyz.zhouxy.plusone.sms;
|
||||||
|
|
||||||
|
import com.tencentcloudapi.common.Credential;
|
||||||
|
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||||
|
import com.tencentcloudapi.common.profile.ClientProfile;
|
||||||
|
import com.tencentcloudapi.common.profile.HttpProfile;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||||
|
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用腾讯 SMS 服务
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class TencentSmsServiceImpl implements SmsService {
|
||||||
|
|
||||||
|
private final SmsProperties properties;
|
||||||
|
private final Credential credential;
|
||||||
|
|
||||||
|
public TencentSmsServiceImpl(SmsProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
SmsCredentialProperties smsCredential = properties.getCredential();
|
||||||
|
this.credential = new Credential(smsCredential.getSecretId(), smsCredential.getSecretKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendCodeMessage(String code, String phoneNumber) {
|
||||||
|
sendCodeMessage("ZhouXY", code, phoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendCodeMessage(String signName, String code, String phoneNumber) {
|
||||||
|
String[] phoneNums = {"+86" + phoneNumber};
|
||||||
|
sendMessage(signName, "code", phoneNums, code, "10");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(String signName, String action, String[] phoneNumberSet, String... templateParamSet) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
SmsClient client = getClient();
|
||||||
|
SendSmsRequest req = getSendSmsRequest(signName,
|
||||||
|
properties.getTemplates().get(action),
|
||||||
|
phoneNumberSet,
|
||||||
|
templateParamSet);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 返回的 res 是一个
|
||||||
|
* SendSmsResponse 类的实例,与请求对象对应
|
||||||
|
*/
|
||||||
|
// var res = client.SendSms(req);
|
||||||
|
client.SendSms(req);
|
||||||
|
|
||||||
|
// 输出json格式的字符串回包
|
||||||
|
// System.out.println(SendSmsResponse.toJsonString(res));
|
||||||
|
|
||||||
|
// 也可以取出单个值,你可以通过官网接口文档或跳转到response对象的定义处查看返回字段的定义
|
||||||
|
// System.out.println(res.getRequestId());
|
||||||
|
|
||||||
|
} catch (TencentCloudSDKException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendSmsRequest getSendSmsRequest(String signName,
|
||||||
|
String templateId,
|
||||||
|
String[] phoneNumberSet,
|
||||||
|
String... templateParamSet) {
|
||||||
|
SendSmsRequest req = new SendSmsRequest();
|
||||||
|
|
||||||
|
String sdkAppId = properties.getAppId();
|
||||||
|
req.setSmsSdkAppId(sdkAppId);
|
||||||
|
req.setSignName(signName);
|
||||||
|
|
||||||
|
/* 国际/港澳台短信 SenderId: 国内短信填空,默认未开通,如需开通请联系 [sms helper] */
|
||||||
|
String senderId = "";
|
||||||
|
req.setSenderId(senderId);
|
||||||
|
|
||||||
|
/* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
|
||||||
|
// String sessionContext = "xxx";
|
||||||
|
// req.setSessionContext(sessionContext);
|
||||||
|
|
||||||
|
/* 短信号码扩展号: 默认未开通,如需开通请联系 [sms helper] */
|
||||||
|
String extendCode = "";
|
||||||
|
req.setExtendCode(extendCode);
|
||||||
|
|
||||||
|
/* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */
|
||||||
|
req.setTemplateId(templateId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] 示例如:+8613711112222, 其中前面有一个+号
|
||||||
|
* ,86为国家码,13711112222为手机号,最多不要超过200个手机号
|
||||||
|
*/
|
||||||
|
req.setPhoneNumberSet(phoneNumberSet);
|
||||||
|
|
||||||
|
/* 模板参数: 若无模板参数,则设置为空 */
|
||||||
|
req.setTemplateParamSet(templateParamSet);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SmsClient getClient() {
|
||||||
|
String region = properties.getRegion();
|
||||||
|
ClientProfile clientProfile = getClientProfile();
|
||||||
|
return new SmsClient(credential, region, clientProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientProfile getClientProfile() {
|
||||||
|
HttpProfile httpProfile = getHttpProfile();
|
||||||
|
/*
|
||||||
|
* 非必要步骤: 实例化一个客户端配置对象,可以指定超时时间等配置
|
||||||
|
*/
|
||||||
|
ClientProfile clientProfile = new ClientProfile();
|
||||||
|
String signMethod = properties.getClient().getSignMethod();
|
||||||
|
if (signMethod != null) {
|
||||||
|
clientProfile.setSignMethod(signMethod);
|
||||||
|
} else {
|
||||||
|
clientProfile.setSignMethod("HmacSHA256");
|
||||||
|
}
|
||||||
|
clientProfile.setHttpProfile(httpProfile);
|
||||||
|
return clientProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpProfile getHttpProfile() {
|
||||||
|
HttpProfile httpProfile = new HttpProfile();
|
||||||
|
SmsHttpProperties smsHttp = properties.getClient().getHttp();
|
||||||
|
if (smsHttp != null) {
|
||||||
|
if (smsHttp.getReqMethod() != null) {
|
||||||
|
httpProfile.setReqMethod(smsHttp.getReqMethod());
|
||||||
|
}
|
||||||
|
if (smsHttp.getConnTimeout() != null) {
|
||||||
|
httpProfile.setConnTimeout(60);
|
||||||
|
}
|
||||||
|
SmsProxyProperties proxy = smsHttp.getProxy();
|
||||||
|
if (proxy != null && proxy.getHost() != null && proxy.getPort() != null) {// 设置代理
|
||||||
|
httpProfile.setProxyHost(proxy.getHost());
|
||||||
|
httpProfile.setProxyPort(proxy.getPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return httpProfile;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package xyz.zhouxy.plusone.spring;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
public class SpringContextHolder {
|
||||||
|
|
||||||
|
private ApplicationContext context;
|
||||||
|
|
||||||
|
private static final SpringContextHolder INSTANCE = new SpringContextHolder();
|
||||||
|
|
||||||
|
private SpringContextHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApplicationContext getContext() {
|
||||||
|
return INSTANCE.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class SpringContextHolderConfig {
|
||||||
|
@Bean(name = "springContextHolder")
|
||||||
|
SpringContextHolder getSpringContextHolder(ApplicationContext context) {
|
||||||
|
SpringContextHolder.INSTANCE.context = context;
|
||||||
|
return SpringContextHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package xyz.zhouxy.plusone.sql;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.ibatis.jdbc.AbstractSQL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展 MyBatis 中的 SQL 语句构造器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public class SQL extends AbstractSQL<SQL> {
|
||||||
|
|
||||||
|
public SQL SET_IF(boolean condition, String sets) {
|
||||||
|
return condition ? SET(sets) : getSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQL SET_IF_NOT_NULL(Object param, String sets) {
|
||||||
|
return Objects.nonNull(param) ? SET(sets) : getSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQL WHERE_IF(boolean condition, String sqlCondition) {
|
||||||
|
return condition ? WHERE(sqlCondition) : getSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQL WHERE_IF_NOT_NULL(Object param, String sqlCondition) {
|
||||||
|
return Objects.nonNull(param) ? WHERE(sqlCondition) : getSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQL getSelf() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验器
|
||||||
|
*
|
||||||
|
* <p>可以使用以下方式初始化一个校验器:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* BaseValidator<Integer> validator = new BaseValidator<>() {
|
||||||
|
* {
|
||||||
|
* ruleFor(value -> Objects.nonNull(value), "value 不能为空");
|
||||||
|
* ruleFor(value -> (value >= 0 && value <= 500), "value 应在 [0, 500] 内");
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>也可以通过继承本类,定义一个校验器(可使用单例模式)。</p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 然后通过校验器的 {@link #validate} 方法,或
|
||||||
|
* {@link ValidateUtil#validate(Object, Validator)} 对指定对象进行校验。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ValidateUtil.validate(255, validator);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* validator.validate(666);
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see IValidateRequired
|
||||||
|
* @see ValidateUtil
|
||||||
|
* @see Validator
|
||||||
|
*/
|
||||||
|
public abstract class BaseValidator<T> {
|
||||||
|
|
||||||
|
private final List<RuleInfo<T>> rules = new ArrayList<>();
|
||||||
|
|
||||||
|
protected BaseValidator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void ruleFor(Predicate<T> rule, String errorMessage) {
|
||||||
|
this.rules.add(new RuleInfo<>(rule, errorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(T obj) {
|
||||||
|
this.rules.forEach((RuleInfo<T> ruleInfo) -> {
|
||||||
|
if (!ruleInfo.rule.test(obj)) {
|
||||||
|
throw new InvalidInputException(ruleInfo.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class RuleInfo<T> {
|
||||||
|
Predicate<T> rule;
|
||||||
|
String message;
|
||||||
|
|
||||||
|
public RuleInfo(Predicate<T> rule, String message) {
|
||||||
|
this.rule = rule;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import cn.hutool.core.exceptions.ValidateException;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||||
|
import xyz.zhouxy.plusone.util.RegexUtil;
|
||||||
|
|
||||||
|
public abstract class BaseValidator2<T> {
|
||||||
|
|
||||||
|
private List<ValidValueHolder<T, ?>> hs = new ArrayList<>();
|
||||||
|
|
||||||
|
protected final <R> ValidValueHolder<T, R> ruleFor(Function<T, R> getter) {
|
||||||
|
ValidValueHolder<T, R> validValueHolder = new ValidValueHolder<>(getter);
|
||||||
|
hs.add(validValueHolder);
|
||||||
|
return validValueHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(T obj) {
|
||||||
|
for (var holder : hs) {
|
||||||
|
var value = holder.getter.apply(obj);
|
||||||
|
for (var rule : holder.rules) {
|
||||||
|
if (!rule.condition.test(value)) {
|
||||||
|
throw new ValidateException(rule.errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidValueHolder<T, R> {
|
||||||
|
Function<T, R> getter;
|
||||||
|
|
||||||
|
List<RuleInfo<Object>> rules = new ArrayList<>();
|
||||||
|
|
||||||
|
public ValidValueHolder(Function<T, R> getter) {
|
||||||
|
this.getter = getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRule(Predicate<Object> condition, String errMsg) {
|
||||||
|
this.rules.add(new RuleInfo<>(condition, errMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> nonNull(String errMsg) {
|
||||||
|
addRule(Objects::nonNull, errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> nonEmpty(String errMsg) {
|
||||||
|
addRule(value -> {
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value instanceof Collection) {
|
||||||
|
return ((Collection<?>) value).isEmpty();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
return ((String) value).isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> size(int min, int max, String errMsg) {
|
||||||
|
addRule(value -> {
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value instanceof Collection) {
|
||||||
|
int size = ((Collection<?>) value).size();
|
||||||
|
return size >= min && size <= max;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> length(int min, int max, String errMsg) {
|
||||||
|
addRule(value -> {
|
||||||
|
if (value == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
int length = ((String) value).length();
|
||||||
|
return length >= min && length <= max;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> matches(String regex, String errMsg) {
|
||||||
|
addRule(input -> RegexUtil.matches(regex, (String) input), errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> matchesOr(String[] regexs, String errMsg) {
|
||||||
|
addRule(input -> RegexUtil.matchesOr((String) input, regexs), errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> matchesAnd(String[] regexs, String errMsg) {
|
||||||
|
addRule(input -> RegexUtil.matchesAnd((String) input, regexs), errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidValueHolder<T, R> email(String errMsg) {
|
||||||
|
return matches(RegexConsts.EMAIL, errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ValidValueHolder<T, R> and(Predicate<R> condition, String errMsg) {
|
||||||
|
addRule((Predicate<Object>) condition, errMsg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
static final class RuleInfo<R> {
|
||||||
|
Predicate<R> condition;
|
||||||
|
String errMsg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DtoValidator {
|
||||||
|
Class<?> value();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自带校验方法,校验不通过时直接抛异常。
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*
|
||||||
|
* @see ValidateUtil
|
||||||
|
* @see BaseValidator
|
||||||
|
*/
|
||||||
|
public interface IValidateRequired {
|
||||||
|
void validate();
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Before;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
class ValidateDtosConfig {
|
||||||
|
|
||||||
|
final Map<Class<?>, BaseValidator<?>> validatorMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
ValidateDtosConfig(ApplicationContext context) {
|
||||||
|
Map<String, Object> beans = context.getBeansWithAnnotation(DtoValidator.class);
|
||||||
|
for (var validator : beans.values()) {
|
||||||
|
Class<?> targetClass = validator.getClass().getAnnotation(DtoValidator.class).value();
|
||||||
|
this.validatorMap.put(targetClass, (BaseValidator<?>) validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before("@annotation(ValidateDto) && args(dto)")
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> void doValidateDto(T dto) {
|
||||||
|
BaseValidator<T> validator = (BaseValidator<T>) this.validatorMap.get(dto.getClass());
|
||||||
|
if (validator != null) {
|
||||||
|
validator.validate(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ValidateDto {
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验工具类
|
||||||
|
* <p>
|
||||||
|
* 对 {@link IValidateRequired} 的实现类对象进行校验
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*
|
||||||
|
* @see BaseValidator
|
||||||
|
* @see Validator
|
||||||
|
* @see IValidateRequired
|
||||||
|
*/
|
||||||
|
public class ValidateUtil {
|
||||||
|
private ValidateUtil() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validate(Object obj) {
|
||||||
|
if (obj instanceof IValidateRequired) {
|
||||||
|
((IValidateRequired) obj).validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void validate(T obj, BaseValidator<T> validator) {
|
||||||
|
validator.validate(obj);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验器
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 可以使用以下方式初始化一个校验器:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* var validator = new Validator<Integer>()
|
||||||
|
* .addRule(value -> Objects.nonNull(value), "value 不能为空")
|
||||||
|
* .addRule(value -> (value >= 0 && value <= 500), "value 应在 [0, 500] 内");
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 然后通过校验器的 {@link #validate} 方法,或
|
||||||
|
* {@link ValidateUtil#validate(Object, Validator)} 对指定对象进行校验。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* validator.validate(666);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ValidateUtil.validate(255, validator);
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see IValidateRequired
|
||||||
|
* @see ValidateUtil
|
||||||
|
* @see BaseValidator
|
||||||
|
*/
|
||||||
|
public final class Validator<T> extends BaseValidator<T> {
|
||||||
|
public final Validator<T> addRule(final Predicate<T> rule, final String errorMessage) {
|
||||||
|
ruleFor(rule, errorMessage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package xyz.zhouxy.plusone.web.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域配置
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebCorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOriginPatterns("*")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.allowedMethods("GET", "POST", "OPTIONS", "PUT", "DELETE", "PATCH")
|
||||||
|
.maxAge(3600);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package xyz.zhouxy.plusone.web.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.core.convert.converter.ConverterFactory;
|
||||||
|
import org.springframework.format.FormatterRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring MVC 配置。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 将与前后端交互的数据中的枚举转换为其整数值
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebEnumMapConfig implements WebMvcConfigurer {
|
||||||
|
@Override
|
||||||
|
public void addFormatters(FormatterRegistry registry) {
|
||||||
|
registry.addConverterFactory(new IntToEnumConverterFactory());
|
||||||
|
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IntToEnumConverterFactory implements ConverterFactory<Integer, Enum<?>> {
|
||||||
|
@Override
|
||||||
|
public <T extends Enum<?>> Converter<Integer, T> getConverter(Class<T> targetType) {
|
||||||
|
return (Integer source) -> {
|
||||||
|
try {
|
||||||
|
T[] values = targetType.getEnumConstants();
|
||||||
|
return values[source];
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new EnumConstantNotPresentException(targetType, Integer.toString(source));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
|
||||||
|
@Override
|
||||||
|
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
|
||||||
|
return (String source) -> {
|
||||||
|
int index = Integer.parseInt(source);
|
||||||
|
try {
|
||||||
|
T[] values = targetType.getEnumConstants();
|
||||||
|
return values[index];
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new EnumConstantNotPresentException(targetType, Integer.toString(index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
spring.application.name=${plusone.application.name}
|
||||||
|
|
||||||
|
spring.mail.host=${plusone.mail.host}
|
||||||
|
spring.mail.username=${plusone.mail.from}
|
||||||
|
spring.mail.password=${plusone.mail.password}
|
||||||
|
spring.mail.default-encoding=UTF-8
|
||||||
|
spring.mail.properties.mail.debug=${plusone.debug}
|
||||||
|
|
||||||
|
server.port=${plusone.server.port}
|
||||||
|
|
||||||
|
mybatis-plus.global-config.banner=false
|
||||||
|
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||||
|
mybatis-plus.global-config.db-config.logic-delete-field=deleted
|
||||||
|
# 逻辑已删除值(默认为 1)
|
||||||
|
mybatis-plus.global-config.db-config.logic-delete-value=id
|
||||||
|
# 逻辑未删除值(默认为 0)
|
||||||
|
mybatis-plus.global-config.db-config.logic-not-delete-value=0
|
||||||
|
mybatis-plus.configuration.default-enum-type-handler=org.apache.ibatis.type.EnumOrdinalTypeHandler
|
||||||
|
|
||||||
|
# Sa-Token 配置
|
||||||
|
sa-token.is-print=false
|
||||||
|
# token名称 (同时也是cookie名称)
|
||||||
|
sa-token.token-name=${plusone.application.name}
|
||||||
|
# 是否允许同一账号并发登录 (为true时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||||
|
sa-token.is-concurrent=true
|
||||||
|
# 在多人登录同一账号时,是否共用一个token (为 true 时所有登录共用一个 token, 为false时每次登录新建一个 token)
|
||||||
|
sa-token.is-share=true
|
||||||
|
sa-token.token-style=simple-uuid
|
||||||
|
sa-token.activity-timeout=1800
|
||||||
|
sa-token.is-read-cookie=false
|
||||||
|
sa-token.is-log=${plusone.debug}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package xyz.zhouxy.plusone.validator;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||||
|
|
||||||
|
class BaseValidator2Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValid() {
|
||||||
|
LoginCommand loginCommand = new LoginCommand("13169053215@qq.com", "8GouTDE53a", false);
|
||||||
|
LoginCommandValidator.INSTANCE.validate(loginCommand);
|
||||||
|
System.err.println(loginCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
class LoginCommand {
|
||||||
|
private String account;
|
||||||
|
private String pwd;
|
||||||
|
private boolean rememberMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginCommandValidator extends BaseValidator2<LoginCommand> {
|
||||||
|
|
||||||
|
public static final LoginCommandValidator INSTANCE = new LoginCommandValidator();
|
||||||
|
|
||||||
|
private LoginCommandValidator() {
|
||||||
|
ruleFor(loginCommand -> loginCommand.getAccount())
|
||||||
|
.nonNull("邮箱地址不能为空")
|
||||||
|
.matchesOr(new String[] { RegexConsts.EMAIL, RegexConsts.MOBILE_PHONE }, "请输入邮箱地址或手机号");
|
||||||
|
ruleFor(loginCommand -> loginCommand.getPwd())
|
||||||
|
.nonNull("密码不能为空")
|
||||||
|
.matches(RegexConsts.PASSWORD, "密码格式错误");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>plusone</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>plusone-basic</name>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>plusone-basic-common</module>
|
||||||
|
<module>plusone-basic-domain</module>
|
||||||
|
<module>plusone-basic-infrastructure</module>
|
||||||
|
<module>plusone-basic-application</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-common</artifactId>
|
||||||
|
<version>${plusone-basic.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-domain</artifactId>
|
||||||
|
<version>${plusone-basic.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-infrastructure</artifactId>
|
||||||
|
<version>${plusone-basic.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-application</artifactId>
|
||||||
|
<version>${plusone-basic.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>plusone</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-start</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>plusone-start</name>
|
||||||
|
|
||||||
|
<description>参考 DDD 落地的脚手架</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring-boot.version>2.7.6</spring-boot.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-system-application</artifactId>
|
||||||
|
<version>${plusone-system.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.source}</source>
|
||||||
|
<target>${maven.compiler.target}</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>xyz.zhouxy.plusone.PlusoneApplication</mainClass>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>repackage</id>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@Slf4j
|
||||||
|
public class PlusoneApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
log.debug("Plusone started!");
|
||||||
|
SpringApplication.run(PlusoneApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "plusone.application.name",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "A description for 'plusone.application.name'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plusone.server.port",
|
||||||
|
"type": "java.lang.Integer",
|
||||||
|
"description": "A description for 'plusone.server.port'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plusone.debug",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "A description for 'plusone.debug'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plusone.mail.host",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "A description for 'plusone.mail.host'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plusone.mail.password",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "A description for 'plusone.mail.password'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "plusone.exception.handle-all-exception",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "A description for 'plusone.exception.handle-all-exception'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
plusone:
|
||||||
|
application:
|
||||||
|
name: plusone
|
||||||
|
server:
|
||||||
|
port: 8108
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
# 短信发送相关参数
|
||||||
|
sms:
|
||||||
|
region: ap-guangzhou
|
||||||
|
credential:
|
||||||
|
secret-id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
secret-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
client:
|
||||||
|
# SDK 默认用 TC3-HMAC-SHA256 进行签名,非必要请不要修改这个字段
|
||||||
|
sign-method: HmacSHA256
|
||||||
|
http:
|
||||||
|
# proxy:
|
||||||
|
# host: xxx
|
||||||
|
# port: xxx
|
||||||
|
req-method: POST
|
||||||
|
conn-timeout: 60
|
||||||
|
app-id: 1111111111
|
||||||
|
templates:
|
||||||
|
code: 0000000
|
||||||
|
|
||||||
|
# 邮件发送相关参数
|
||||||
|
mail:
|
||||||
|
host: smtp.163.com
|
||||||
|
#设置邮件发送者
|
||||||
|
from: example@163.com
|
||||||
|
password: XXXXXXXXXXXXXXXX
|
||||||
|
subject:
|
||||||
|
code: Plusone
|
||||||
|
template:
|
||||||
|
code: 【Plusone】验证码:%s,10分钟内有效,请勿泄露。
|
||||||
|
|
||||||
|
# 异常拦截机制是否拦截所有异常
|
||||||
|
exception:
|
||||||
|
handle-all-exception: false
|
||||||
|
|
||||||
|
spring:
|
||||||
|
# redis配置
|
||||||
|
redis:
|
||||||
|
# Redis数据库索引(默认为0)
|
||||||
|
database: 1
|
||||||
|
# Redis服务器地址
|
||||||
|
host: 127.0.0.1
|
||||||
|
# Redis服务器连接端口
|
||||||
|
port: 6379
|
||||||
|
# Redis服务器连接密码(默认为空)
|
||||||
|
# password:
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池最大连接数
|
||||||
|
max-active: 200
|
||||||
|
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 10
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 数据库
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/plusone
|
||||||
|
username: plusone
|
||||||
|
password: XXXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
logging:
|
||||||
|
file:
|
||||||
|
name: ${user.home}/logs/${plusone.application.name}.log
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
xyz.zhouxy.plusone: DEBUG
|
||||||
|
org.springframework.jdbc.core: DEBUG
|
||||||
|
xyz.zhouxy.plusone.system.application.query: DEBUG
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
|
||||||
|
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%5level) ${PID:- } - [%15.15t] %cyan(%-40.108logger) : %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5level ${PID:- } - [%15.15t] %-50.50logger : %msg%n</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<file>log/output.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
|
||||||
|
<fileNamePattern>log/output.log.%i</fileNamePattern>
|
||||||
|
</rollingPolicy>
|
||||||
|
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||||
|
<MaxFileSize>20MB</MaxFileSize>
|
||||||
|
</triggeringPolicy>
|
||||||
|
</appender> -->
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<!-- <appender-ref ref="FILE" /> -->
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -0,0 +1,22 @@
|
||||||
|
package xyz.zhouxy.plusone;
|
||||||
|
|
||||||
|
import java.io.ObjectStreamClass;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class SerialTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSerialVersionUID() {
|
||||||
|
var cl = PlusoneException.class;
|
||||||
|
var c = ObjectStreamClass.lookup(cl);
|
||||||
|
var uid = c.getSerialVersionUID();
|
||||||
|
log.info("\n @java.io.Serial" +
|
||||||
|
"\n private static final long serialVersionUID = {}L;", uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.zhouxy.plusone;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.LoginInfoViewObject;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.AdminLoginService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.LoginByPasswordCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.account.AccountRepository;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = PlusoneApplication.class)
|
||||||
|
class TestAop {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
AccountRepository repository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
AdminLoginService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAop() {
|
||||||
|
LoginByPasswordCommand command = new LoginByPasswordCommand();
|
||||||
|
command.setPrincipal("Code108");
|
||||||
|
command.setPassword("2333");
|
||||||
|
command.setRememberMe(false);
|
||||||
|
LoginInfoViewObject loginInfo = service.loginByPassword(command);
|
||||||
|
System.err.println(loginInfo);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>plusone-system</artifactId>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>plusone-system-application</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-system-domain</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-system-infrastructure</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>xyz.zhouxy</groupId>
|
||||||
|
<artifactId>plusone-basic-application</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,61 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.common.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.domain.IWithOrderNumber;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.MenuViewObject;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.menu.Menu.MenuType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单工具类
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public class MenuUtil {
|
||||||
|
private MenuUtil() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建菜单树
|
||||||
|
*
|
||||||
|
* @param allMenus 菜单列表
|
||||||
|
* @return 菜单树
|
||||||
|
*/
|
||||||
|
public static List<MenuViewObject> buildMenuTree(Collection<MenuViewObject> allMenus) {
|
||||||
|
// 先排序,保证添加到 rootMenus 中的顺序,以及 addChild 添加的子菜单的顺序
|
||||||
|
allMenus = allMenus.stream()
|
||||||
|
.sorted(Comparator.comparing(IWithOrderNumber::getOrderNumber))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 一级菜单
|
||||||
|
List<MenuViewObject> rootMenus = new ArrayList<>();
|
||||||
|
// key: 菜单 id; value: 菜单对象. 方便根据 id 查找相应对象。
|
||||||
|
Map<Long, MenuViewObject> menuListMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (var menu : allMenus) {
|
||||||
|
// 添加 MENU_LIST 到 map 中,方便后面调用对象的方法
|
||||||
|
if (menu.getType() == MenuType.MENU_LIST.ordinal()) {
|
||||||
|
menuListMap.put(menu.getId(), menu);
|
||||||
|
}
|
||||||
|
// 一级菜单
|
||||||
|
if (menu.getParentId() == 0) {
|
||||||
|
rootMenus.add(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var menu : allMenus) {
|
||||||
|
var parent = menuListMap.getOrDefault(menu.getParentId(), null);
|
||||||
|
// 父菜单存在于 map 中,调用父菜单的 addChild 方法将当前菜单添加为父菜单的子菜单。
|
||||||
|
if (parent != null) {
|
||||||
|
parent.addChild(menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootMenus;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.common.util;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import xyz.zhouxy.plusone.constant.RegexConsts;
|
||||||
|
|
||||||
|
public enum PrincipalType {
|
||||||
|
EMAIL(RegexConsts.EMAIL),
|
||||||
|
MOBILE_PHONE(RegexConsts.MOBILE_PHONE),
|
||||||
|
USERNAME(RegexConsts.USERNAME)
|
||||||
|
;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String regex;
|
||||||
|
|
||||||
|
PrincipalType(String regex) {
|
||||||
|
this.regex = regex;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.common.util;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.account.Email;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.account.MobilePhone;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.account.Principal;
|
||||||
|
import xyz.zhouxy.plusone.system.domain.model.account.Username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字面值,判断并生成 {@link Principal} 值对象。
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
* @see Principal
|
||||||
|
* @see Username
|
||||||
|
* @see Email
|
||||||
|
* @see MobilePhone
|
||||||
|
* @see InvalidInputException
|
||||||
|
*/
|
||||||
|
public class PrincipalUtil {
|
||||||
|
|
||||||
|
private PrincipalUtil() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrincipalType getPrincipalType(@Nullable String principal) {
|
||||||
|
if (principal == null) {
|
||||||
|
throw new IllegalArgumentException("principal 不能为空");
|
||||||
|
}
|
||||||
|
PrincipalType[] principalTypes = PrincipalType.values();
|
||||||
|
for (var principalType : principalTypes) {
|
||||||
|
if (principal.matches(principalType.getRegex())) {
|
||||||
|
return principalType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Principal getPrincipal(@Nullable String principal) {
|
||||||
|
PrincipalType principalType = getPrincipalType(principal);
|
||||||
|
if (principalType == PrincipalType.EMAIL) {
|
||||||
|
return Email.of(principal);
|
||||||
|
}
|
||||||
|
if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||||
|
return MobilePhone.of(principal);
|
||||||
|
}
|
||||||
|
if (principalType == PrincipalType.USERNAME) {
|
||||||
|
return Username.of(principal);
|
||||||
|
}
|
||||||
|
throw InvalidInputException.unsupportedPrincipalTypeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Principal getEmailOrMobilePhone(@Nullable String principal) {
|
||||||
|
PrincipalType principalType = getPrincipalType(principal);
|
||||||
|
if (principalType == PrincipalType.EMAIL) {
|
||||||
|
return Email.of(principal);
|
||||||
|
}
|
||||||
|
if (principalType == PrincipalType.MOBILE_PHONE) {
|
||||||
|
return MobilePhone.of(principal);
|
||||||
|
}
|
||||||
|
throw InvalidInputException.unsupportedPrincipalTypeException("输入邮箱地址或手机号");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.AccountContextService;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号查询本身相关信息
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("account")
|
||||||
|
public class AccountContextController {
|
||||||
|
|
||||||
|
private final AccountContextService service;
|
||||||
|
|
||||||
|
public AccountContextController(AccountContextService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("info")
|
||||||
|
public RestfulResult getAccountInfo() {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
var result = service.getAccountInfo();
|
||||||
|
return RestfulResult.success("查询成功", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("menus")
|
||||||
|
public RestfulResult getMenuTree() {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
var result = service.getMenuTree();
|
||||||
|
return RestfulResult.success("查询成功", result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||||
|
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.AccountManagementService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.CreateAccountCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.UpdateAccountCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.AssertResult;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号管理
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("sys/account")
|
||||||
|
public class AccountManagementController {
|
||||||
|
|
||||||
|
private final AccountManagementService service;
|
||||||
|
|
||||||
|
public AccountManagementController(AccountManagementService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public RestfulResult createAccount(@RequestBody @Valid CreateAccountCommand command) {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
adminAuthLogic.checkPermission("sys-account-create");
|
||||||
|
service.createAccount(command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
public RestfulResult deleteAccounts(@RequestBody List<Long> ids) {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
adminAuthLogic.checkPermission("sys-account-delete");
|
||||||
|
service.deleteAccounts(ids);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("{id}")
|
||||||
|
public RestfulResult updateAccountInfo(
|
||||||
|
@PathVariable("id") Long id,
|
||||||
|
@RequestBody @Valid UpdateAccountCommand command) {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
adminAuthLogic.checkPermission("sys-account-update");
|
||||||
|
service.updateAccountInfo(id, command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("query")
|
||||||
|
public RestfulResult queryAccountOverviewList(AccountQueryParams queryParams) {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
adminAuthLogic.checkPermission("sys-account-list");
|
||||||
|
var accountOverviewList = service.queryAccountOverviewList(queryParams);
|
||||||
|
return success("查询成功", accountOverviewList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{accountId}")
|
||||||
|
public RestfulResult queryAccountDetails(@PathVariable("accountId") Long accountId) {
|
||||||
|
adminAuthLogic.checkLogin();
|
||||||
|
adminAuthLogic.checkPermission("sys-account-details");
|
||||||
|
var accountDetails = service.queryAccountDetails(accountId);
|
||||||
|
AssertResult.nonNull(accountDetails);
|
||||||
|
return success("查询成功", accountDetails);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.AdminLoginService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.LoginByOtpCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.LoginByPasswordCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin 账号登录
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("login")
|
||||||
|
public class AdminLoginController {
|
||||||
|
|
||||||
|
private final AdminLoginService service;
|
||||||
|
|
||||||
|
public AdminLoginController(AdminLoginService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("byPassword")
|
||||||
|
public RestfulResult loginByPassword(@RequestBody LoginByPasswordCommand command) {
|
||||||
|
var loginInfo = service.loginByPassword(command);
|
||||||
|
return success("登录成功", loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("byOtp")
|
||||||
|
public RestfulResult loginByOtp(@RequestBody LoginByOtpCommand command) {
|
||||||
|
var loginInfo = service.loginByOtp(command);
|
||||||
|
return success("登录成功", loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("sendOtp")
|
||||||
|
public RestfulResult sendOtp(@RequestParam String principal) {
|
||||||
|
service.sendOtp(principal);
|
||||||
|
return success("发送成功");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.AdminLogoutService;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin 账号登出
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("logout")
|
||||||
|
public class AdminLogoutController {
|
||||||
|
|
||||||
|
private final AdminLogoutService service;
|
||||||
|
|
||||||
|
public AdminLogoutController(AdminLogoutService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public RestfulResult execute() {
|
||||||
|
service.execute();
|
||||||
|
return RestfulResult.success("注销成功");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||||
|
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.DictManagementService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.CreateDictCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.UpdateDictCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典管理
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("sys/dict")
|
||||||
|
public class DictManagementController {
|
||||||
|
|
||||||
|
private final DictManagementService service;
|
||||||
|
|
||||||
|
public DictManagementController(DictManagementService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public RestfulResult createDict(@RequestBody @Valid CreateDictCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-dict-create");
|
||||||
|
service.createDict(command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
public RestfulResult deleteDicts(@RequestBody List<Long> ids) {
|
||||||
|
adminAuthLogic.checkPermission("sys-dict-delete");
|
||||||
|
service.deleteDicts(ids);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("{id}")
|
||||||
|
public RestfulResult updateDict(
|
||||||
|
@PathVariable("id") Long id,
|
||||||
|
@RequestBody @Valid UpdateDictCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-dict-update");
|
||||||
|
service.updateDict(id, command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{dictId}")
|
||||||
|
public RestfulResult findDictDetails(@PathVariable("dictId") Long dictId) {
|
||||||
|
adminAuthLogic.checkPermission("sys-dict-details");
|
||||||
|
var dictDetails = service.findDictDetails(dictId);
|
||||||
|
return success("查询成功", dictDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("all")
|
||||||
|
public RestfulResult loadAllDicts() {
|
||||||
|
adminAuthLogic.checkPermissionAnd("sys-dict-list", "sys-dict-details");
|
||||||
|
var dicts = service.loadAllDicts();
|
||||||
|
return success("查询成功", dicts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("query")
|
||||||
|
public RestfulResult queryDictOverviewList(@Valid DictQueryParams queryParams) {
|
||||||
|
adminAuthLogic.checkPermission("sys-dict-list");
|
||||||
|
var dicts = service.queryDictOverviewList(queryParams);
|
||||||
|
return success("查询成功", dicts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||||
|
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.MenuManagementService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.CreateMenuCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.UpdateMenuCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单管理
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("sys/menu")
|
||||||
|
public class MenuManagementController {
|
||||||
|
|
||||||
|
private final MenuManagementService service;
|
||||||
|
|
||||||
|
public MenuManagementController(MenuManagementService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== create ====================
|
||||||
|
@PostMapping
|
||||||
|
public RestfulResult createMenu(@RequestBody @Valid CreateMenuCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-create");
|
||||||
|
service.createMenu(command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== delete ====================
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
public RestfulResult deleteMenu(@PathVariable("id") Long id) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-delete");
|
||||||
|
service.deleteMenu(id);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== update ====================
|
||||||
|
@PatchMapping("{id}")
|
||||||
|
public RestfulResult updateMenu(
|
||||||
|
@PathVariable("id") Long id,
|
||||||
|
@RequestBody @Valid UpdateMenuCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-update");
|
||||||
|
service.updateMenu(id, command);
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== query ====================
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public RestfulResult findById(@PathVariable("id") Long id) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-details");
|
||||||
|
var result = service.findById(id);
|
||||||
|
return RestfulResult.success("查询成功", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("queryByAccountId")
|
||||||
|
public RestfulResult queryByAccountId(@RequestParam Long accountId) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-details");
|
||||||
|
var result = service.queryByAccountId(accountId);
|
||||||
|
return success("查询成功", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("queryByRoleId")
|
||||||
|
public RestfulResult queryByRoleId(@RequestParam Long roleId) {
|
||||||
|
adminAuthLogic.checkPermission("sys-menu-details");
|
||||||
|
var result = service.queryByRoleId(roleId);
|
||||||
|
return success("查询成功", result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import static xyz.zhouxy.plusone.util.RestfulResult.success;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.RegisterAccountService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.RegisterAccountCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册账号服务
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("register")
|
||||||
|
public class RegisterAccountController {
|
||||||
|
|
||||||
|
private final RegisterAccountService service;
|
||||||
|
|
||||||
|
public RegisterAccountController(RegisterAccountService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public RestfulResult registerAccount(@RequestBody RegisterAccountCommand command) {
|
||||||
|
service.registerAccount(command);
|
||||||
|
return success("注册成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("sendCode")
|
||||||
|
public RestfulResult sendCode(@RequestParam String principal) {
|
||||||
|
service.sendCode(principal);
|
||||||
|
return success("发送成功");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.controller;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import static xyz.zhouxy.plusone.system.constant.AuthLogic.adminAuthLogic;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.RoleManagementService;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.CreateRoleCommand;
|
||||||
|
import xyz.zhouxy.plusone.system.application.service.command.UpdateRoleCommand;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色管理服务
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("sys/role")
|
||||||
|
public class RoleManagementController {
|
||||||
|
|
||||||
|
private final RoleManagementService service;
|
||||||
|
|
||||||
|
public RoleManagementController(RoleManagementService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public RestfulResult createRole(@RequestBody @Valid CreateRoleCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-role-create");
|
||||||
|
service.createRole(command);
|
||||||
|
return RestfulResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping
|
||||||
|
public RestfulResult updateRole(@RequestBody @Valid UpdateRoleCommand command) {
|
||||||
|
adminAuthLogic.checkPermission("sys-role-update");
|
||||||
|
service.updateRole(command);
|
||||||
|
return RestfulResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
public RestfulResult delete(@PathVariable("id") Long id) {
|
||||||
|
adminAuthLogic.checkPermission("sys-role-delete");
|
||||||
|
service.delete(id);
|
||||||
|
return RestfulResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("exists")
|
||||||
|
public RestfulResult exists(@RequestParam("id") Long id) {
|
||||||
|
adminAuthLogic.checkPermissionOr("sys-role-list", "sys-role-details");
|
||||||
|
var isExists = service.exists(id);
|
||||||
|
return RestfulResult.success(isExists ? "存在" : "不存在", isExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public RestfulResult findById(@PathVariable("id") Long id) {
|
||||||
|
adminAuthLogic.checkPermission("sys-role-details");
|
||||||
|
var result = service.findById(id);
|
||||||
|
return RestfulResult.success("查询成功", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("query")
|
||||||
|
public RestfulResult query(RoleQueryParams params) {
|
||||||
|
adminAuthLogic.checkPermission("sys-role-list");
|
||||||
|
var result = service.query(params);
|
||||||
|
return RestfulResult.success("查询成功", result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class AccountLoginException extends PlusoneException {
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = -3040996790739138556L;
|
||||||
|
|
||||||
|
private static final int DEFAULT_ERR_CODE = 4030000;
|
||||||
|
|
||||||
|
private AccountLoginException() {
|
||||||
|
super(DEFAULT_ERR_CODE, "用户登录异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountLoginException(int code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountLoginException accountNotExistException() {
|
||||||
|
return new AccountLoginException(4030101, "用户账户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountLoginException otpErrorException() {
|
||||||
|
return new AccountLoginException(4030501, "验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountLoginException otpNotExistsException() {
|
||||||
|
return new AccountLoginException(4030502, "验证码不存在或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountLoginException passwordErrorException() {
|
||||||
|
return new AccountLoginException(4030200, "用户密码错误");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.PlusoneException;
|
||||||
|
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public class AccountRegisterException extends PlusoneException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = 7580245181633370195L;
|
||||||
|
|
||||||
|
public AccountRegisterException() {
|
||||||
|
this(4020000, "用户注册错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountRegisterException(String message) {
|
||||||
|
this(4020000, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountRegisterException(Throwable cause) {
|
||||||
|
super(4020000, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountRegisterException(int code, String message) {
|
||||||
|
super(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountRegisterException(int code, Throwable cause) {
|
||||||
|
super(code, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException emailOrMobilePhoneRequiredException() {
|
||||||
|
return new AccountRegisterException(4020300, "邮箱和手机号应至少绑定一个");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException usernameAlreadyExists(String username) {
|
||||||
|
return new AccountRegisterException(4020400, String.format("用户名 %s 已存在", username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException emailAlreadyExists(String value) {
|
||||||
|
return new AccountRegisterException(4020500, String.format("邮箱 %s 已存在", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException mobilePhoneAlreadyExists(String value) {
|
||||||
|
return new AccountRegisterException(4020600, String.format("手机号 %s 已存在", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException codeErrorException() {
|
||||||
|
return new AccountRegisterException(4020701, "校验码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountRegisterException codeNotExistsException() {
|
||||||
|
return new AccountRegisterException(4020702, "校验码不存在或已过期");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.exception;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.exception.InvalidInputException;
|
||||||
|
|
||||||
|
public class UnsupportedMenuTypeException extends InvalidInputException {
|
||||||
|
|
||||||
|
@java.io.Serial
|
||||||
|
private static final long serialVersionUID = -769169844015637730L;
|
||||||
|
|
||||||
|
public UnsupportedMenuTypeException() {
|
||||||
|
this("不支持的菜单类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsupportedMenuTypeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.exception.handler;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.DisableServiceException;
|
||||||
|
import cn.dev33.satoken.exception.SameTokenInvalidException;
|
||||||
|
import cn.dev33.satoken.exception.NotBasicAuthException;
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
|
import cn.dev33.satoken.exception.NotPermissionException;
|
||||||
|
import cn.dev33.satoken.exception.NotRoleException;
|
||||||
|
import cn.dev33.satoken.exception.NotSafeException;
|
||||||
|
import cn.dev33.satoken.exception.SaTokenException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xyz.zhouxy.plusone.exception.handler.BaseExceptionHandler;
|
||||||
|
import xyz.zhouxy.plusone.util.RestfulResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sa-Token 异常处理器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class SaTokenExceptionHandler extends BaseExceptionHandler {
|
||||||
|
|
||||||
|
public SaTokenExceptionHandler(ExceptionInfoHolder exceptionInfoHolder) {
|
||||||
|
super(exceptionInfoHolder);
|
||||||
|
set(NotPermissionException.class, 4030103, "会话未能通过权限认证", HttpStatus.FORBIDDEN);
|
||||||
|
set(NotRoleException.class, 4030103, "会话未能通过角色认证", HttpStatus.FORBIDDEN);
|
||||||
|
set(DisableServiceException.class, 4030202, "账号指定服务已被封禁", HttpStatus.FORBIDDEN);
|
||||||
|
set(SameTokenInvalidException.class, 4030400, "提供的 Same-Token 无效", HttpStatus.UNAUTHORIZED);
|
||||||
|
set(NotBasicAuthException.class, 4030000, "会话未能通过 Http Basic 认证", HttpStatus.UNAUTHORIZED);
|
||||||
|
set(NotSafeException.class, 4020300, "会话未能通过二级认证", HttpStatus.UNAUTHORIZED);
|
||||||
|
set(NotLoginException.class,
|
||||||
|
4020400,
|
||||||
|
e -> switch (((NotLoginException) e).getType()) {
|
||||||
|
case NotLoginException.NOT_TOKEN -> "未提供 Token";
|
||||||
|
case NotLoginException.INVALID_TOKEN -> "Token 无效";
|
||||||
|
case NotLoginException.TOKEN_TIMEOUT -> "Token 已过期";
|
||||||
|
case NotLoginException.BE_REPLACED -> "Token 已被顶下线";
|
||||||
|
case NotLoginException.KICK_OUT -> "Token 已被踢下线";
|
||||||
|
default -> "当前会话未登录";
|
||||||
|
},
|
||||||
|
HttpStatus.UNAUTHORIZED);
|
||||||
|
set(SaTokenException.class, 4020300, "未通过身份认证或权限认证", HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(SaTokenException.class)
|
||||||
|
public ResponseEntity<RestfulResult> handleSaTokenException(SaTokenException e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return buildExceptionResponse(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.AccountQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.AccountDetails;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.AccountOverview;
|
||||||
|
import xyz.zhouxy.plusone.util.PageDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号信息查询器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AccountQueries {
|
||||||
|
|
||||||
|
default PageDTO<AccountOverview> queryAccountOverviewPage(AccountQueryParams queryParams) {
|
||||||
|
List<AccountOverview> content = queryAccountOverview(queryParams);
|
||||||
|
long total = count(queryParams);
|
||||||
|
return PageDTO.of(content, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AccountOverview> queryAccountOverview(AccountQueryParams queryParams);
|
||||||
|
|
||||||
|
long count(AccountQueryParams queryParams);
|
||||||
|
|
||||||
|
AccountDetails queryAccountDetails(Long accountId);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||||
|
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.sql.SQL;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.DictQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.DictOverview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典查询器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DictQueries {
|
||||||
|
|
||||||
|
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public DictQueries(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DictOverview> queryDictOverviewList(DictQueryParams queryParams) {
|
||||||
|
String sql = new SQL()
|
||||||
|
.SELECT("id", "dict_type", "dict_label",
|
||||||
|
"created_by", "create_time", "updated_by", "update_time", "count")
|
||||||
|
.FROM("view_sys_dict_overview")
|
||||||
|
.WHERE_IF(queryParams.getDictType() != null, "dict_type LIKE '%:dictType%'")
|
||||||
|
.WHERE_IF(queryParams.getDictLabel() != null, "dict_label LIKE '%:dictLabel%'")
|
||||||
|
.toString();
|
||||||
|
return this.jdbcTemplate
|
||||||
|
.query(sql, new BeanPropertySqlParameterSource(queryParams), new BeanPropertyRowMapper<>(DictOverview.class));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
public interface PermissionQueries {
|
||||||
|
// TODO【添加】 权限信息查询器
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||||
|
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
|
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import xyz.zhouxy.plusone.sql.SQL;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.params.RoleQueryParams;
|
||||||
|
import xyz.zhouxy.plusone.system.application.query.result.RoleOverview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色信息查询器
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class RoleQueries {
|
||||||
|
|
||||||
|
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public RoleQueries(NamedParameterJdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询 Role
|
||||||
|
* <p>
|
||||||
|
* <b> !!!注意:此方法内存在字符串拼接,勿在循环内使用。</b>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param params 查询参数
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
public List<RoleOverview> query(RoleQueryParams params) {
|
||||||
|
String b = new SQL()
|
||||||
|
.SELECT("id")
|
||||||
|
.FROM("sys_role")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getId(), "id = :id")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getName(), "name = :name")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getIdentifier(), "identifier = :identifier")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getStatus(), "status = :status")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getCreateTimeStart(), "create_time >= :createTimeStart")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getCreateTimeEnd(), "create_time < :createTimeEnd")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getUpdateTimeStart(), "update_time >= :updateTimeStart")
|
||||||
|
.WHERE_IF_NOT_NULL(params.getUpdateTimeEnd(), "update_time < :updateTimeEnd")
|
||||||
|
.LIMIT(params.getSize())
|
||||||
|
.OFFSET(params.getOffset())
|
||||||
|
.toString();
|
||||||
|
var sql = """
|
||||||
|
SELECT a.id AS id, a.name AS name, a.identifier AS identifier, a.status AS status
|
||||||
|
FROM sys_role AS a, (
|
||||||
|
""" + b + """
|
||||||
|
) AS b
|
||||||
|
WHERE a.id = b.id
|
||||||
|
""";
|
||||||
|
return this.jdbcTemplate
|
||||||
|
.query(sql, new BeanPropertySqlParameterSource(params),
|
||||||
|
new BeanPropertyRowMapper<>(RoleOverview.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query.params;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号信息查询参数
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@ToString
|
||||||
|
public class AccountQueryParams extends PagingAndSortingQueryParams {
|
||||||
|
|
||||||
|
public AccountQueryParams() {
|
||||||
|
super("id",
|
||||||
|
"username",
|
||||||
|
"email",
|
||||||
|
"mobile_phone",
|
||||||
|
"status",
|
||||||
|
"nickname",
|
||||||
|
"sex",
|
||||||
|
"created_by",
|
||||||
|
"create_time",
|
||||||
|
"updated_by",
|
||||||
|
"update_time");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO【添加】 注解参数校验
|
||||||
|
private @Getter @Setter Long id;
|
||||||
|
private @Getter @Setter String username;
|
||||||
|
private @Getter @Setter String email;
|
||||||
|
private @Getter @Setter String mobilePhone;
|
||||||
|
private @Getter @Setter Integer status;
|
||||||
|
private @Getter @Setter String nickname;
|
||||||
|
private @Getter @Setter Integer sex;
|
||||||
|
private @Getter @Setter Long createdBy;
|
||||||
|
private @Getter @Setter LocalDate createTimeStart;
|
||||||
|
private @Getter LocalDate createTimeEnd;
|
||||||
|
private @Getter @Setter Long updatedBy;
|
||||||
|
private @Getter @Setter LocalDate updateTimeStart;
|
||||||
|
private @Getter LocalDate updateTimeEnd;
|
||||||
|
private @Getter @Setter Long roleId;
|
||||||
|
|
||||||
|
public void setCreateTimeEnd(LocalDate createTimeEnd) {
|
||||||
|
this.createTimeEnd = createTimeEnd.plusDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTimeEnd(LocalDate updateTimeEnd) {
|
||||||
|
this.updateTimeEnd = updateTimeEnd.plusDays(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package xyz.zhouxy.plusone.system.application.query.params;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import xyz.zhouxy.plusone.util.PagingAndSortingQueryParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典查询参数
|
||||||
|
*
|
||||||
|
* @author <a href="https://gitee.com/zhouxy108">ZhouXY</a>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class DictQueryParams extends PagingAndSortingQueryParams {
|
||||||
|
String dictType;
|
||||||
|
String dictLabel;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue