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