This commit is contained in:
Bunny 2024-11-11 12:03:29 +08:00
commit 68636e5209
371 changed files with 22648 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Bunny
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
logs/
!**/src/main/resources/application-prod.yml
### 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/

943
ReadMe.md Normal file
View File

@ -0,0 +1,943 @@
# 项目预览
不知道为什么图床用的使自己的Gitee就是不显示其它GitHub和Gitea都能显示就Gitee显示不出来
或者把项目clone下来看也可以
**线上地址**
- 正式线上预览地址http://111.229.137.235/#/welcome
- 测试预览地址http://106.15.251.123/#/welcome
- 服务器到期时间12月30日
**Github地址**
- [前端地址](https://github.com/BunnyMaster/bunny-admin-web.git)
- [后端地址](https://github.com/BunnyMaster/bunny-admin-server)
**Gitee地址**
- [前端地址](https://gitee.com/BunnyBoss/bunny-admin-web)
- [后端地址](https://gitee.com/BunnyBoss/bunny-admin-server)
## 环境搭建
### 安装docker内容
如果使用是centos或者是rocky
```shell
# 更新yum 和 dnf
yum update -y
dnf update -y
# 安装必要依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 设置镜像源
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum list docker-ce --showduplicates | sort -r
# 安装docker
yum -y install docker-ce.x86_64
# 开机启动docker
systemctl enable docker
systemctl start docker
```
### 安装Redis
#### 编写配置文件
```sh
mkdir /bunny/docker_data/my_redis/ -p
vim /bunny/docker_data/my_redis/redis.conf
```
**添加以下内容**
有注释大概率启动不了
```
# bind 127.0.0.1 #注释掉这部分使redis可以外部访问
daemonize no #用守护线程的方式启动
requirepass 123456
appendonly yes #redis持久化  默认是no
tcp-keepalive 300 #防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300
```
**删除注释**
```
daemonize no
requirepass 123456
appendonly yes
tcp-keepalive 300
```
#### 启动Redis
```sh
docker pull redis:7.0.10
docker run -p 6379:6379 --name redis_master \
-v /bunny/docker_data/redis_master/redis.conf:/etc/redis/redis.conf \
-v/bunny/docker_data/redis_master/data:/data \
--restart=always -d redis:7.0.10 --appendonly yes
```
### 安装Minio
```sh
docker run -d \
-p 9000:9000 \
-p 9090:9090 \
--name minio_master --restart=always \
-v /bunny/docker/minio/data:/data \
-e "MINIO_ROOT_USER=bunny" \
-e "MINIO_ROOT_PASSWORD=02120212" \
minio/minio server /data --console-address ":9090"
```
### 安装MySQL
**设置开机启动**
**执行启动3306**
```sh
docker stop master
docker rm master
docker run --name master -p 3306:3306 \
-v /bunny/docker_data/mysql/master/etc/my.cnf:/etc/my.cnf \
-v /bunny/docker_data/mysql/master/data:/var/lib/mysql \
--restart=always --privileged=true \
-e MYSQL_ROOT_PASSWORD=02120212 \
-e TZ=Asia/Shanghai \
mysql:8.0.33 --lower-case-table-names=1
```
**执行启动3304**
其中有创建备份目录
```shell
docker stop slave_3304
docker rm slave_3304
docker run --name slave_3304 -p 3304:3306 \
-v /bunny/docker_data/mysql/slave_3304/etc/my.cnf:/etc/my.cnf \
-v /bunny/docker_data/mysql/slave_3304/data:/var/lib/mysql \
-v /bunny/docker_data/mysql/slave_3304/backup:\
--restart=always --privileged=true \
-e MYSQL_ROOT_PASSWORD=02120212 \
-e TZ=Asia/Shanghai \
mysql:8.0.33 --lower-case-table-names=1
```
**修改密码:**
```sh
docker exec -it mysql_master /bin/bash
mysql -uroot -p02120212
use mysql
ALTER USER 'root'@'%' IDENTIFIED BY "02120212";
FLUSH PRIVILEGES;
```
> my.cnf 配置
>
> ```sql
> [mysqld]
> skip-host-cache
> skip-name-resolve
> secure-file-priv=/var/lib/mysql-files
> user=mysql
>
> # 设置字符集
> character-set-server=utf8mb4
> collation-server=utf8mb4_unicode_ci
>
> # 设置服务器ID如果是复制集群确保每个节点的ID唯一
> server-id=1
>
> # 启用二进制日志
> log-bin=mysql-bin
>
> # 设置表名不区分大小写
> lower_case_table_names = 1
>
> ```
### 数据库文件
在后端文件的根目录中
![image-20241107133345299](http://116.196.101.14:9000/docs/image-20241107133345299.png)
# 项目特点
### 按钮权限显示
如果当前用户在这个页面中只有【添加】和【删除】那么页面按钮中只会显示出【添加按钮】和【删除按钮】
### 去除前后空格
后端配置了自动去除前端传递的空字符串,如果传递的内容前后有空格会自动去除前后的空格
![image-20241105215241811](http://116.196.101.14:9000/docs/image-20241105215241811.png)
代码内容
```java
@ControllerAdvice
public class ControllerStringParamTrimConfig {
/**
* 创建 String trim 编辑器
* 构造方法中 boolean 参数含义为如果是空白字符串,是否转换为null
* 即如果为true,那么 " " 会被转换为 null,否者为 ""
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
StringTrimmerEditor propertyEditor = new StringTrimmerEditor(false);
// 为 String 类对象注册编辑器
binder.registerCustomEditor(String.class, propertyEditor);
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
// 为 String 类型自定义反序列化操作
jacksonObjectMapperBuilder
.deserializerByType(String.class, new StdScalarDeserializer<String>(String.class) {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
// 去除全部空格
// return StringUtils.trimAllWhitespace(jsonParser.getValueAsString());
// 仅去除前后空格
return jsonParser.getValueAsString().trim();
}
});
};
}
}
```
### 项目接口和页面
接口地址有两个:
1. knife4j
2. swagger
接口地址://localhost:7070/doc.html#/home
![image-20241105213953503](http://116.196.101.14:9000/docs/image-20241105213953503.png)
swagger接口地址http://localhost:7070/swagger-ui/index.html
![image-20241105214100720](http://116.196.101.14:9000/docs/image-20241105214100720.png)
前端接口地址http://localhost:7000/#/welcome
![image-20241105214230389](http://116.196.101.14:9000/docs/image-20241105214230389.png)
## 登录功能
可以选择邮箱登录或者是密码直接登录,两者不互用。
### 账号登录
![image-20241105212146456](http://116.196.101.14:9000/docs/image-20241105212146456.png)
#### 业务需求
- 用户输入用户名和密码进行登录
#### 实现思路
- 用户输入账号和密码和数据库中账号密码进行比对,成功后进行页面跳转
- 如果账户禁用会显示账户已封禁
**后端实现文件位置**
- 拦截请求为`/admin/login`的请求之后进行登录验证的判断
![image-20241105212722043](http://116.196.101.14:9000/docs/image-20241105212722043.png)
### 邮箱登录
![image-20241105212255972](http://116.196.101.14:9000/docs/image-20241105212255972.png)
#### 业务需求
- 用户输入邮箱账号、密码、邮箱验证码之后进行登录
#### 实现思路
- 需要验证用户输入的邮箱格式是否正确。
- 在未输入验证码的情况下输入密码会提示用户同时后端也会进行验证。如果输入了邮箱验证码但是Redis中不存在或已过期会提示邮箱验证码不存在或已过期。
- 之后对邮箱账号和密码进行判断包括邮箱验证码进行判断
- 判断逻辑如下,文件位置如上图所示。
```java
/**
* * 自定义验证
* 判断邮箱验证码是否正确
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
ObjectMapper objectMapper = new ObjectMapper();
try {
loginDto = objectMapper.readValue(request.getInputStream(), LoginDto.class);
// type不能为空
String type = loginDto.getType();
if (!StringUtils.hasText(type)) {
out(response, Result.error(ResultCodeEnum.REQUEST_IS_EMPTY));
return null;
}
String emailCode = loginDto.getEmailCode();
String username = loginDto.getUsername();
String password = loginDto.getPassword();
// 如果有邮箱验证码,表示是邮箱登录
if (type.equals("email")) {
emailCode = emailCode.toLowerCase();
Object redisEmailCode = redisTemplate.opsForValue().get(RedisUserConstant.getAdminUserEmailCodePrefix(username));
if (redisEmailCode == null) {
out(response, Result.error(ResultCodeEnum.EMAIL_CODE_EMPTY));
return null;
}
// 判断用户邮箱验证码是否和Redis中发送的验证码
if (!emailCode.equals(redisEmailCode.toString().toLowerCase())) {
out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING));
return null;
}
}
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
return getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
out(response, Result.error(ResultCodeEnum.ILLEGAL_DATA_REQUEST));
return null;
}
}
```
## 首页功能
![image-20241105212403630](http://116.196.101.14:9000/docs/image-20241105212403630.png)
功能菜单,首页图表展示部分功能已经由这个模板作者设计好,其中需要注意的是,如果要查看历史消息或者是进入消息页面可以双击![image-20241105213346408](http://116.196.101.14:9000/docs/image-20241105213346408.png)既可进入消息页面
### 消息功能
![image-20241105213539594](http://116.196.101.14:9000/docs/image-20241105213539594-1730813844820-2.png)
#### 业务需求
1. 消息页面的展示,包含删除、批量删除、选中已读和当前页面所有消息都标为已读
2. 当用户对左侧菜单点击时可以过滤出消息内容,展示不同的消息类型
![image-20241105213720011](http://116.196.101.14:9000/docs/image-20241105213720011.png)
3. 可以点击已读和全部进行筛选消息
![image-20241105214342220](http://116.196.101.14:9000/docs/image-20241105214342220.png)
3. 可以根据标题进行搜搜
4. 包含分页
#### 实现思路
1. 显示当前消息类型,用户点击时带参数请求,只带当前消息类型,不默认携带已读状态查询,然后从数据库筛选并返回结果。
2. 点击"已读"选项时,若选择"全部"之前是设置为undefined这样就不会携带参数了但是底下会有警告现在改为空字符串后端只需过滤掉空字符串即可。
3. 删除选定数据若用户选择列表并筛选出所有ID将数据传递给后端用户删除为逻辑删除
4. 全部标为已读![image-20241106131949217](http://116.196.101.14:9000/docs/image-20241106131949217.png)类似删除操作筛选出选中数据的ID然后传递给后端以标记为已读。
5. 将所有数据标记为已读当前页面前端使用map提取所有ID整合成ID列表传递给后端表示页面上所有数据已读。
6. 输入标题后,随输入变化进行搜索。
**后端代码位置**
![image-20241105213922824](http://116.196.101.14:9000/docs/image-20241105213922824.png)
### 用户管理
![image-20241106002713514](http://116.196.101.14:9000/docs/image-20241106002713514.png)
#### 需求分析
1. 用户操作需要包含CURD的操作
2. 为了方便在用户中需要给出快速禁用当前用户按钮
3. 需要显示用户头像、性别、最后登录的IP地址和归属地
4. 在左侧中需要包含部分查询
5. 可以根据点击的部门查询当前部门下的用户
6. 根据用户可以强制下线、管理员可以修改用户密码、为用户分配角色
![image-20241106002908657](http://116.196.101.14:9000/docs/image-20241106002908657.png)
#### 实现思路
**上传头像**
前端需要剪裁图片内容进行上传后端将前端上传的头像存储到Minio中在上传头像中可以有几菜单可以看到功能菜单。
![image-20241106003056116](http://116.196.101.14:9000/docs/image-20241106003056116.png)
右击时可以看到功能菜单,如上传、下载等功能
![image-20241106003154056](http://116.196.101.14:9000/docs/image-20241106003154056.png)
**重置密码**
重置密码需要判断当前用户密码是否是符合指定的密码格式,并且会根据当前输入密码计算得分如果当前密码复杂则得分越高那么密码强度越强
![image-20241106003256994](http://116.196.101.14:9000/docs/image-20241106003256994.png)
重置密码组件在前端的公共组件文件中
![image-20241106003426573](http://116.196.101.14:9000/docs/image-20241106003426573.png)
**分配角色**
- 给用户分配了admin角色后其他路由绑定和权限设置就不再需要了因为后端会根据admin角色在前端用户信息中设置通用权限码如`*`、`*::*`、`*::*::*`,表示前端用户可以访问所有权限并查看所有内容。
- 管理员有权对用户进行角色分配,这涉及到许多操作,包括菜单显示和接口访问权限。角色与权限相关联,角色也与菜单相关联。
- 当用户访问菜单时,会根据其角色看到其所属的菜单内容。随后,角色与权限接口相关联,根据用户的权限来决定是否显示操作按钮。后端会根据用户的权限验证其是否可以访问当前接口。
- 用户登录或刷新页面时会重新获取用户信息,用户信息中包含角色和权限信息。利用角色和权限信息与前端传递的路径进行比对判断,如果用户包含菜单角色,则可以访问。如果用户包含前端路由中的权限,则表示该权限可以访问。后端也会进行权限判断,以防止通过接口文档等方式访问。
- 分配好角色后,菜单会根据当前路由角色匹配用户角色,从而根据用户角色显示相应的菜单内容。
![image-20241106004533031](http://116.196.101.14:9000/docs/image-20241106004533031.png)
### 角色管理
角色管理包含CURD和权限分配操作
![image-20241106132548236](http://116.196.101.14:9000/docs/image-20241106132548236.png)
#### 业务需求
用户对角色进行CURD操作点击权限设置时让用户分配权限
#### 实现思路
1. 在设计的表中,如果存在相同的角色码,系统会提示用户当前角色已经存在。
![image-20241106132938024](http://116.196.101.14:9000/docs/image-20241106132938024.png)
2. 后端会根据角色的ID分配权限的ID列表。
![image-20241106135600255](http://116.196.101.14:9000/docs/image-20241106135600255.png)
3. 后端在角色权限表中会根据角色的ID分配权限内容。在角色权限表中会先删除当前角色所有的权限内容然后再进行权限内容的重新分配。
```java
public void assignPowersToRole(AssignPowersToRoleDto dto) {
List<Long> powerIds = dto.getPowerIds();
Long roleId = dto.getRoleId();
// 删除这个角色下所有权限
baseMapper.deleteBatchRoleIdsWithPhysics(List.of(roleId));
// 保存分配数据
List<RolePower> rolePowerList = powerIds.stream().map(powerId -> {
RolePower rolePower = new RolePower();
rolePower.setRoleId(roleId);
rolePower.setPowerId(powerId);
return rolePower;
}).toList();
saveBatch(rolePowerList);
// 找到所有和当前更新角色相同的用户
List<Long> roleIds = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery().eq(UserRole::getRoleId, roleId))
.stream().map(UserRole::getUserId).toList();
// 根据Id查找所有用户
List<AdminUser> adminUsers = userMapper.selectList(Wrappers.<AdminUser>lambdaQuery().in(!roleIds.isEmpty(), AdminUser::getId, roleIds));
// 用户为空时不更新Redis的key
if (adminUsers.isEmpty()) return;
// 更新Redis中用户信息
List<Long> userIds = adminUsers.stream().map(AdminUser::getId).toList();
roleFactory.updateUserRedisInfo(userIds);
}
```
### 权限管理
![image-20241106135954104](http://116.196.101.14:9000/docs/image-20241106135954104.png)
![image-20241106140006176](http://116.196.101.14:9000/docs/image-20241106140006176.png)
在权限配置中,添加/修改权限时的请求地址为后端接口的请求地址,请求地址使用了【`正则表达式`】判断和【`antpath`】方式填写
> ### 正则表达式
>
> #### 作用和用法:
>
> - **作用**:正则表达式用于描述字符串的特征,可以用来匹配、查找、替换等字符串操作。
> - **用法**在Java中可以使用`java.util.regex`包来支持正则表达式的使用。例如,可以使用`Pattern`和`Matcher`类来编译和匹配正则表达式。
>
> #### 示例:
>
> ```java
> // 匹配邮箱地址的正则表达式示例
> String emailRegex = "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b";
> String email = "example@email.com";
>
> Pattern pattern = Pattern.compile(emailRegex);
> Matcher matcher = pattern.matcher(email);
>
> if (matcher.find()) {
> System.out.println("Valid email address");
> } else {
> System.out.println("Invalid email address");
> }
> ```
>
> ### Ant Path
>
> #### 作用和用法:
>
> - **作用**Ant Path是Spring框架中用来匹配URL路径的一种模式匹配方式类似于Unix系统中的路径匹配规则。
> - **用法**在Spring中Ant Path可以用来匹配URL路径例如在配置Spring的URL映射时可以使用Ant Path来指定匹配规则。
>
> #### 示例:
>
> ```java
> // Ant Path示例
> String pattern = "/users/*/profile";
> String path = "/users/123/profile";
>
> AntPathMatcher matcher = new AntPathMatcher();
> if (matcher.match(pattern, path)) {
> System.out.println("Pattern matched!");
> } else {
> System.out.println("Pattern not matched!");
> }
> ```
>
> Ant Path中支持一些通配符例如`*`匹配任意字符(除了路径分隔符),`**`匹配任意字符包括路径分隔符。Ant Path是一种方便的路径匹配方式可以用来匹配URL路径、文件路径等。
#### 业务需求
1. 对权限表进行CURD操作
2. 在表格中点击新增时父级id为当前点击行的id
#### 实现思路
点击当前行父级id为当前的行的id
![image-20241106140420845](http://116.196.101.14:9000/docs/image-20241106140420845.png)
#### 权限判断实现方式
##### 后端判断方式
判断权限是否可以访问,后端实现判断逻辑
![image-20241106003921315](http://116.196.101.14:9000/docs/image-20241106003921315.png)
##### 前端判断方式
角色分配方式有下面几种想洗参考https://pure-admin.github.io/vue-pure-admin/#/permission/button/router[文档页面](https://pure-admin.github.io/pure-admin-doc/pages/routerMenu/#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%B7%AF%E7%94%B1%E7%9A%84-name-%E5%BF%85%E5%86%99-%E8%80%8C%E4%B8%94%E5%BF%85%E9%A1%BB%E5%94%AF%E4%B8%80)
1. 使用标签方式
![image-20241106004247600](http://116.196.101.14:9000/docs/image-20241106004247600.png)
2. 使用函数方式
![image-20241106004310635](http://116.196.101.14:9000/docs/image-20241106004310635.png)
3. 使用指令方式
![image-20241106005252328](http://116.196.101.14:9000/docs/image-20241106005252328.png)
在前端utils文件夹下有`auth.ts`文件里面包含了权限码信息,如果当前菜单属性中包含这个权限码表示可以访问这个权限
![image-20241106004433489](http://116.196.101.14:9000/docs/image-20241106004433489.png)
![image-20241106004500855](http://116.196.101.14:9000/docs/image-20241106004500855.png)
### 菜单管理
![image-20241106140545328](http://116.196.101.14:9000/docs/image-20241106140545328.png)
### 菜单路由
在做菜单返回时必须要了解角色和权限表
![image-20241105213516679](http://116.196.101.14:9000/docs/image-20241105213516679.png)
#### 需求分析
1. 从数据库中返回出所有的菜单,其中需要整合成前端所要的形式,需要包含`roles`和`auths`,及其其它参数。
2. 用户需要根据自己的角色访问不同的菜单。
3. 如果当前用户不可以访问某些按钮需要隐藏。
4. 用户通过其它手段访问如swagger、knife4j、apifox、postman这种工具访问需要做权限验证如果当前用户不满足访问这些接口后端需要拒绝。
5. 如果已经添加了菜单名称、路由等级、路由路径会提示`xxx已存在`![image-20241106132818902](http://116.196.101.14:9000/docs/image-20241106132818902.png)
6. 在数据库中为部分字段建立了唯一索引
![image-20241106132908309](http://116.196.101.14:9000/docs/image-20241106132908309.png)
#### 实现思路
1. 角色和权限哪些可以访问的页面交给前端,在模板中已经设计好,如果用户访问了自己看不到的菜单会出现`403`页面;判断方式是根据后端返回的菜单中如果包含当前用户的角色就表示可以访问当前的菜单,如果用户信息中没有这个角色则表示不可以访问这个页面。
2. 页面是否可以访问只是在操作上,如果用户通过接口访问是阻止不了的,所以这时后端需要在后端中进行判断,当前的访问路径是否是被允许的,也就是这个用户是否有这个权限,权限表设计中包含了请求路径
3. 后端需要判断用户请求这个接口是否有权访问
> 整合成前端格式返回需要递归,后端根据当前用户访问的菜单需要进行递归菜单数据之后返回前端,并将这些菜单绑定的角色放置在`roles`中,之后根据角色查询全新啊相关内容,要将权限内容放置在`auths`中.
>
> 如果包含子菜单需要防止在`children`数组中,后端实现时如果没有子菜单默认是空数组而不是`null`
>
> 大致如下:
>
> ```json
> {
> "menuType": 0,
> "title": "admin_user",
> "path": "/system/admin-user",
> "component": "/system/adminUser/index",
> "meta": {
> "icon": "ic:round-manage-accounts",
> "title": "admin_user",
> "rank": 2,
> "roles": [
> "admin",
> "all_page",
> "system",
> "test"
> ],
> "auths": [
> "message::updateMessage",
> "menuIcon::getMenuIconList",
> "admin::messageReceived",
> "config::getWebConfig",
> "admin::config",
> "i18n::getI18n",
> ....
> ],
> "frameSrc": ""
> },
> "children": [],
> "id": "1841803086252548097",
> "parentId": "1",
> "name": "admin_user",
> "rank": 2
> }
> ```
### 部门管理
![image-20241106140738517](http://116.196.101.14:9000/docs/image-20241106140738517.png)
![image-20241106140728748](http://116.196.101.14:9000/docs/image-20241106140728748.png)
#### 业务需求
1. 包含CURD
2. 在用户管理中可以选择对应的部门
#### 实现思路
1. CURD接口文件如下
![image-20241106140826034](http://116.196.101.14:9000/docs/image-20241106140826034.png)
2. 管理员为用户分配部门
![image-20241106140942278](http://116.196.101.14:9000/docs/image-20241106140942278.png)
### 菜单图标
![image-20241106141037894](http://116.196.101.14:9000/docs/image-20241106141037894.png)
![image-20241106141102601](http://116.196.101.14:9000/docs/image-20241106141102601.png)
#### 业务需求
1. 用户在菜单中可以选择存储在数据库中的图标内容
2. 包含CURD内容
#### 实现思路
后端需要返回接口格式实体类如下
```java
public class MenuIconVo extends BaseUserVo {
@Schema(name = "iconCode", title = "icon类名")
private String iconCode;
@Schema(name = "iconName", title = "icon 名称")
private String iconName;
}
```
![image-20241106141521051](http://116.196.101.14:9000/docs/image-20241106141521051.png)
前端封装好的组件
![image-20241106141626697](http://116.196.101.14:9000/docs/image-20241106141626697.png)
### 邮箱相关
#### 业务需求
1. 邮件用户配置CURD
2. 邮件模板CURD
3. 邮件用户中只能有一个是默认的,如果当前修改其它项需要将其它已经启用改为不启用
4. 邮件模板需要绑定邮件用户
### 实现思路
邮件模板中添加或者修改时前端需要返回所有的邮件模板用户添加或者修改时将用户ID存储在邮件模板的数据字段中
![image-20241106141920350](http://116.196.101.14:9000/docs/image-20241106141920350.png)
### web配置
![image-20241106142001190](http://116.196.101.14:9000/docs/image-20241106142001190.png)
### 系统监控
#### 服务监控
从SpringBoot的Actuator中获取信息页面采用响应式
![image-20241106142208794](http://116.196.101.14:9000/docs/image-20241106142208794.png)
#### 系统缓存
当前内容被SpringBoot缓存会显示在这
![image-20241106142253759](http://116.196.101.14:9000/docs/image-20241106142253759.png)
### 定时任务
采用Quarter持久化存储所有的可以使用的定时任务都在这
![image-20241106142429924](http://116.196.101.14:9000/docs/image-20241106142429924.png)
#### 页面展示
![image-20241106142449033](http://116.196.101.14:9000/docs/image-20241106142449033.png)
![](http://116.196.101.14:9000/docs/image-20241106142449033-1730874298898-1.png)
### 多语言管理
![image-20241106142531047](http://116.196.101.14:9000/docs/image-20241106142531047.png)
![image-20241106142544172](http://116.196.101.14:9000/docs/image-20241106142544172.png)
### 日志管理
![image-20241106142606017](http://116.196.101.14:9000/docs/image-20241106142606017.png)
![image-20241106142614917](http://116.196.101.14:9000/docs/image-20241106142614917.png)
### 消息管理
管理员可以发送消息告诉xxx用户在主页中会显示![image-20241106142908363](http://116.196.101.14:9000/docs/image-20241106142908363.png)
之后点击时会看到消息封面、标题、简介、消息等级、消息等级内容
![image-20241106142949366](http://116.196.101.14:9000/docs/image-20241106142949366.png)
#### 消息类型
![image-20241106143008098](http://116.196.101.14:9000/docs/image-20241106143008098.png)
包含CURD用户编辑消息发送时可以在选择
![image-20241106144017015](http://116.196.101.14:9000/docs/image-20241106144017015.png)
同时在用户消息栏中也会显示对应内容
![image-20241106144050996](http://116.196.101.14:9000/docs/image-20241106144050996.png)
前端判断逻辑如下
![image-20241106144146081](http://116.196.101.14:9000/docs/image-20241106144146081.png)
#### 消息编辑
提供md编辑器和富文本编辑器
![image-20241106144223976](http://116.196.101.14:9000/docs/image-20241106144223976.png)
![image-20241106144246068](http://116.196.101.14:9000/docs/image-20241106144246068.png)
消息接受用户,如果不填写表示全部的用户,填写后会根据填写的内容存储在用户接受表中![image-20241106144522442](http://116.196.101.14:9000/docs/image-20241106144522442.png)
![image-20241106144449463](http://116.196.101.14:9000/docs/image-20241106144449463.png)
消息等级是显示消息样式颜色,文字内容为消息简介内容
![image-20241106144407453](http://116.196.101.14:9000/docs/image-20241106144407453.png)
#### 消息接收管理
根据上面所选的接受用户会出现在下面的用户接受表中,可以对当前用户是否已读进行修改
![image-20241106144307885](http://116.196.101.14:9000/docs/image-20241106144307885.png)
#### 消息发送管理
之前编辑过的消息都会在这
![image-20241106144317746](http://116.196.101.14:9000/docs/image-20241106144317746.png)
# 环境部署
使用Docker进行部署后端接口地址以`/admin`开头,但前端默认请求前缀为`/api`,因此在请求时需要进行替换。详细内容请参考以下【项目部署】说明。
## 配置相关
### docker文件
```dockerfile
# 使用官方的 Nginx 镜像作为基础镜像
FROM nginx
# 删除默认的 Nginx 配置文件
RUN rm /etc/nginx/conf.d/default.conf
# 将自定义的 Nginx 配置文件复制到容器中
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 设置时区,构建镜像时执行的命令
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
# 创建一个目录来存放前端项目文件
WORKDIR /usr/share/nginx/html
# 将前端项目打包文件复制到 Nginx 的默认静态文件目录
COPY dist/ /usr/share/nginx/html
# 复制到nginx目录下
COPY dist/ /etc/nginx/html
# 暴露 Nginx 的默认端口
EXPOSE 80
# 自动启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
```
### NGINX文件
在请求中会使用代理所以会拿不到用户真实的IP地址素以在要NGINX侠做下面的配置这样用户在访问时就可以拿到真实的IP了
```nginx
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
```
#### 如果需要使用https协议
```dockerfile
COPY bunny-web.site.csr /etc/nginx/bunny-web.site.csr
COPY bunny-web.site.key /etc/nginx/bunny-web.site.key
COPY bunny-web.site_bundle.crt /etc/nginx/bunny-web.site_bundle.crt
COPY bunny-web.site_bundle.pem /etc/nginx/bunny-web.site_bundle.pem
```
NGINX的文件
```nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /etc/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}
# 后端跨域请求
location ~/admin/ {
proxy_pass http://172.17.0.1:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 404 404.html;
location = /50x.html {
root html;
}
}
```
## 项目部署
使用WebStorm进行项目部署项目上线时默认端口为80。因此Docker默认暴露的IP端口也应为80NGINX中默认暴露的端口也是80三者应一一对应。
若无法下载请先使用pnpm下载。若不需使用pnpm请删除或修改相应内容。
![image-20241026025057129](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026025057129.png)
### docker配置
![image-20241026024116090](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026024116090.png)
### 配置环境
设置启动端口号和项目地址机器后端请求地址
![image-20241026024813858](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026024813858.png)
#### 配置线上环境
设置项目启动端口号,线上环境默认请求路径为`/admin`需在NGINX中将访问请求前缀更改为`/admin`。
![image-20241026024940747](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026024940747.png)
![image-20241026024243785](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026024243785.png)
#### 配置开发环境
开发环境默认IP为7000若与本地项目端口冲突请修改。后端请求地址为7070。
前端设置的请求前缀为`/api`,但后端接受的前缀为`/admin`,因此需在服务中修改此内容。
![image-20241026024318644](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026024318644.png)
**修改请求路径**
![image-20241026031651591](http://116.196.101.14:9000/docs/auth/undefinedimage-20241026031651591.png)
### 部署命令
```bash
docker build -f Dockerfile -t bunny_auth_web:1.0.0 . && docker run -p 80:80 --name bunny_auth_web --restart always bunny_auth_web:1.0.0
```

View File

@ -0,0 +1,44 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common-generator</artifactId>
<packaging>jar</packaging>
<name>common-utils</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<!-- 数据库代码生成器 - 新版 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- 驼峰命名互转 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.0-jre</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package cn.bunny.common.generator.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "BaseEntity对象", description = "生成类基础内容")
public class BaseField {
@ApiModelProperty("字段名称")
private String name;
@ApiModelProperty("注释内容")
private String annotation;
@ApiModelProperty("TS类型")
private String type;
@ApiModelProperty("是否必须参数")
private Boolean require;
@ApiModelProperty("是否必须参数消息内容")
private String requireMessage;
}

View File

@ -0,0 +1,21 @@
package cn.bunny.common.generator.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "BaseResultMap对象", title = "数据库基础字段返回映射", description = "数据库基础字段返回映射")
public class BaseResultMap {
@Schema(name = "column", title = "数据库字段")
private String column;
@Schema(name = "property", title = "实体类字段")
private String property;
}

View File

@ -0,0 +1,21 @@
package cn.bunny.common.generator.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "ColumnsField对象", description = "columns列字段名称")
public class ColumnsField {
@ApiModelProperty("列字段名称")
private String name;
@ApiModelProperty("列字段值")
private String value;
}

View File

@ -0,0 +1,26 @@
package cn.bunny.common.generator.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "StoreTypeField对象", description = "仓库类型生成内容")
public class StoreTypeField {
private List<BaseField> baseFieldList;
@ApiModelProperty("接口名称")
private String interfaceName;
@ApiModelProperty("接口注释内容")
private String interfaceAnnotation;
}

View File

@ -0,0 +1,78 @@
package cn.bunny.common.generator.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collections;
public class AdminCodeGenerator {
// 数据连接
// public static final String sqlHost = "jdbc:mysql://192.168.3.98:3304/family_financial?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true";
public static final String sqlHost = "jdbc:mysql://rm-bp12z6hlv46vi6g8mro.mysql.rds.aliyuncs.com:3306/family_financial?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true";
// 作者名称
public static final String author = "Bunny";
// 公共路径
// public static final String outputDir = "D:\\Project\\web\\PC\\financial\\financial-server\\service";
public static final String outputDir = "D:\\MyFolder\\financial\\financial-server\\service";
// 实体类名称
public static final String entity = "Bunny";
public static void main(String[] args) {
Generation("sys_category");
}
/**
* 根据表名生成相应结构代码
*
* @param tableName 表名
*/
public static void Generation(String... tableName) {
// 修改数据库路径账户密码
// FastAutoGenerator.create(sqlHost, "root", "02120212")
FastAutoGenerator.create(sqlHost, "family_financial_prod", "0212family_financial")
.globalConfig(builder -> {
// 添加作者名称
builder.author(author)
// 启用swagger
.enableSwagger()
// 指定输出目录
.outputDir(outputDir + "/src/main/java");
})
.packageConfig(builder -> builder.entity(entity)// 实体类包名
// 父包名如果为空将下面子包名必须写全部 否则就只需写子包名
.parent("cn.bunny.services")
.controller("controller")// 控制层包名
.mapper("mapper")// mapper层包名
.service("service")// service层包名
.serviceImpl("service.impl")// service实现类包名
// 自定义mapper.xml文件输出目录
.pathInfo(Collections.singletonMap(OutputFile.xml, outputDir + "/src/main/resources/mapper")))
.strategyConfig(builder -> {
// 设置要生成的表名
builder.addInclude(tableName)
.addTablePrefix("sys_", "v_", "log_")
.entityBuilder()
.enableLombok()
.enableChainModel()
.naming(NamingStrategy.underline_to_camel)// 数据表映射实体命名策略默认下划线转驼峰underline_to_camel
.columnNaming(NamingStrategy.underline_to_camel)// 表字段映射实体属性命名规则默认null不指定按照naming执行
.idType(IdType.AUTO)// 添加全局主键类型
.formatFileName("%s")// 格式化实体名称%s取消首字母I,
.mapperBuilder()
.mapperAnnotation(Mapper.class)// 开启mapper注解
.enableBaseResultMap()// 启用xml文件中的BaseResultMap 生成
.enableBaseColumnList()// 启用xml文件中的BaseColumnList
.formatMapperFileName("%sMapper")// 格式化Dao类名称
.formatXmlFileName("%sMapper")// 格式化xml文件名称
.serviceBuilder()
.formatServiceFileName("%sService")// 格式化 service 接口文件名称
.formatServiceImplFileName("%sServiceImpl")// 格式化 service 接口文件名称
.controllerBuilder()
.enableRestStyle();
})
.execute();
}
}

View File

@ -0,0 +1,275 @@
package cn.bunny.common.generator.generator;
import cn.bunny.common.generator.entity.BaseField;
import cn.bunny.common.generator.entity.BaseResultMap;
import cn.bunny.common.generator.utils.GeneratorCodeUtils;
import cn.bunny.dao.dto.system.configuration.category.CategoryAddDto;
import cn.bunny.dao.dto.system.configuration.category.CategoryDto;
import cn.bunny.dao.dto.system.configuration.category.CategoryUpdateDto;
import cn.bunny.dao.entity.financial.Category;
import cn.bunny.dao.vo.configuration.CategoryVo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.google.common.base.CaseFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* * 代码生成器入口点
*/
@Service
public class WebGeneratorCode {
// 公共路径
// public static String commonPath = "D:\\Project\\web\\PC\\financial\\financial-web\\src";
public static String commonPath = "D:\\MyFolder\\financial\\financial-web\\src";
// 生成API请求路径
public static String apiPath = commonPath + "\\api\\v1\\financial\\";
// 生成vue路径
public static String vuePath = commonPath + "\\views\\financial\\";
// 生成仓库路径
public static String storePath = commonPath + "\\store\\financial\\";
// 后端controller
public static String controllerPath = "D:\\MyFolder\\financial\\financial-server\\service\\src\\main\\java\\cn\\bunny\\services\\controller\\financial\\";
public static String servicePath = "D:\\MyFolder\\financial\\financial-server\\service\\src\\main\\java\\cn\\bunny\\services\\service\\financial\\";
public static String serviceImplPath = "D:\\MyFolder\\financial\\financial-server\\service\\src\\main\\java\\cn\\bunny\\services\\service\\financial\\impl\\";
public static String mapperPath = "D:\\MyFolder\\financial\\financial-server\\service\\src\\main\\java\\cn\\bunny\\services\\mapper\\financial\\";
public static String resourceMapperPath = "D:\\MyFolder\\financial\\financial-server\\service\\src\\main\\resources\\mapper\\financial\\";
public static void main(String[] args) throws Exception {
Class<?> originalClass = Category.class;
Class<?> dtoClass = CategoryDto.class;
Class<?> addDtoClass = CategoryAddDto.class;
Class<?> updateDtoClass = CategoryUpdateDto.class;
Class<?> voClass = CategoryVo.class;
// 设置velocity资源加载器
Properties prop = new Properties();
prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
// 创建Velocity容器
VelocityContext context = new VelocityContext();
// 原始类名称
String originalName = originalClass.getSimpleName();
// 转成开头小写类名称作为文件名
String lowercaseName = originalName.substring(0, 1).toLowerCase() + originalName.substring(1);
// 转成中划线做vue命名使用
String lowerHyphen = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, originalName);
// 生成字段xxx管理
String classDescription = originalClass.getAnnotation(Schema.class).description();
// 类注解标题
String classTitle = originalClass.getAnnotation(Schema.class).title();
context.put("originalName", originalName);
context.put("lowercaseName", lowercaseName);
context.put("lowerHyphen", lowerHyphen);
context.put("classDescription", classDescription);
context.put("classTitle", classTitle);
context.put("leftBrace", "${");
context.put("date", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 生成前端内容
generatorWebCode(dtoClass, addDtoClass, context);
// 生成后端
generatorServerCode(originalClass, dtoClass, voClass, context);
// 写入文件
writeFiles(lowercaseName, lowerHyphen, originalName, context);
}
/**
* * web端生成字段
*/
public static void generatorWebCode(Class<?> dtoClass, Class<?> addDtoClass, VelocityContext context) {
// 生成 Store form 表单内容
List<BaseField> formList = Arrays.stream(dtoClass.getDeclaredFields())
.filter(field -> !field.getName().equals("id"))
.map(field -> BaseField.builder().name(field.getName()).annotation(field.getAnnotation(Schema.class).title()).build())
.toList();
// 添加表单字段值
List<String> addFormList = Arrays.stream(addDtoClass.getDeclaredFields()).map(Field::getName).toList();
// 是否必须字段设置
List<BaseField> baseFieldList = Arrays.stream(addDtoClass.getDeclaredFields()).map(field -> {
try {
String message = "";
boolean hasMessage = false;
// 验证消息
NotBlank messageAnnotation = field.getAnnotation(NotBlank.class);
if (messageAnnotation != null) {
message = messageAnnotation.message();
hasMessage = StringUtils.hasText(message);
if (!hasMessage) message = field.getAnnotation(NotNull.class).message();
}
// 设置基础字段注解和是否必填项
BaseField baseField = new BaseField();
baseField.setName(field.getName());
baseField.setType(baseField.getType());
baseField.setAnnotation(field.getAnnotation(Schema.class).title());
baseField.setType(GeneratorCodeUtils.convertJavaTypeToTypeScript(field.getType()));
baseField.setRequire(hasMessage);
if (hasMessage) baseField.setRequireMessage(message);
return baseField;
} catch (Exception e) {
throw new RuntimeException(e);
}
}).toList();
// 生成查询表单字段
context.put("formList", formList);
context.put("addFormList", addFormList);
context.put("baseFieldList", baseFieldList);
}
/**
* 生成后端内容
*/
public static void generatorServerCode(Class<?> originalClass, Class<?> dtoClass, Class<?> voClass, VelocityContext context) {
Field[] superFields = originalClass.getSuperclass().getDeclaredFields();
Field[] declaredFields = originalClass.getDeclaredFields();
Field[] mergedArray = new Field[superFields.length + declaredFields.length];
System.arraycopy(superFields, 0, mergedArray, 0, superFields.length);
System.arraycopy(declaredFields, 0, mergedArray, superFields.length, declaredFields.length);
// 添加BaseResultMap
List<BaseResultMap> baseResultMaps = Arrays.stream(mergedArray).map(field -> {
// 转成下划线
String fieldName = field.getName();
String lowerUnderscore = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName);
BaseResultMap baseResultMap = new BaseResultMap();
baseResultMap.setColumn(lowerUnderscore);
baseResultMap.setProperty(fieldName);
return baseResultMap;
}).toList();
// 分页查询内容
List<BaseResultMap> pageQueryMap = Arrays.stream(dtoClass.getDeclaredFields()).map(field -> {
String name = field.getName();
String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
return BaseResultMap.builder().column(column).property(name).build();
}).toList();
// 生层Base_Column_List
String baseColumnList = Stream.of(mergedArray)
.map(field -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()))
.collect(Collectors.joining(", "));
// 表名
String tableName = originalClass.getAnnotation(TableName.class).value();
context.put("baseResultMaps", baseResultMaps);
context.put("type", originalClass.getName());
context.put("voClassType", voClass.getName());
context.put("baseColumnList", baseColumnList);
context.put("tableName", tableName);
context.put("pageQueryMap", pageQueryMap);
}
/**
* * 写入文件
*/
public static void writeFiles(String lowercaseName, String lowerHyphen, String originalName, VelocityContext context) throws IOException {
context.put("apiPath", GeneratorCodeUtils.ReplacePathHandle(apiPath) + lowercaseName);
context.put("storePath", GeneratorCodeUtils.ReplacePathHandle(storePath) + lowercaseName);
context.put("typesPath", GeneratorCodeUtils.ReplacePathHandle(vuePath) + lowercaseName + "/utils/types");
context.put("hookPath", GeneratorCodeUtils.ReplacePathHandle(vuePath) + lowercaseName + "/utils/hooks");
context.put("columnsPath", GeneratorCodeUtils.ReplacePathHandle(vuePath) + lowercaseName + "/utils/columns");
context.put("dialogPath", GeneratorCodeUtils.ReplacePathHandle(vuePath) + lowercaseName + "/" + lowerHyphen + "-dialog.vue");
// 写入api模板
Template apiTemplate = Velocity.getTemplate("vms/web/api.vm", "UTF-8");
FileWriter apiTemplateFileWriter = new FileWriter(apiPath + lowercaseName + ".ts");
apiTemplate.merge(context, apiTemplateFileWriter);
apiTemplateFileWriter.close();
// 写入弹窗模板
Template dialogTemplate = Velocity.getTemplate("vms/web/dialog.vm", "UTF-8");
FileWriter dialogTemplateFileWriter = new FileWriter(vuePath + lowercaseName + "\\" + lowerHyphen + "-dialog.vue");
dialogTemplate.merge(context, dialogTemplateFileWriter);
dialogTemplateFileWriter.close();
// 写入hook模板
Template hookTemplate = Velocity.getTemplate("vms/web/hook.vm", "UTF-8");
FileWriter hookTemplateFileWriter = new FileWriter(vuePath + lowercaseName + "\\utils\\hooks.ts");
hookTemplate.merge(context, hookTemplateFileWriter);
hookTemplateFileWriter.close();
// 写入hook模板
Template storeTemplate = Velocity.getTemplate("vms/web/store.vm", "UTF-8");
FileWriter storeTemplateFileWriter = new FileWriter(storePath + "\\" + lowercaseName + ".ts");
storeTemplate.merge(context, storeTemplateFileWriter);
storeTemplateFileWriter.close();
// 写入types模板
Template typesTemplate = Velocity.getTemplate("vms/web/types.vm", "UTF-8");
FileWriter typesTemplateFileWriter = new FileWriter(vuePath + lowercaseName + "\\utils\\types.ts");
typesTemplate.merge(context, typesTemplateFileWriter);
typesTemplateFileWriter.close();
// 写入index模板
Template indexTemplate = Velocity.getTemplate("vms/web/index.vm", "UTF-8");
FileWriter indexTemplateFileWriter = new FileWriter(vuePath + lowercaseName + "\\index.vue");
indexTemplate.merge(context, indexTemplateFileWriter);
indexTemplateFileWriter.close();
// 写入columns模板
Template columnsTemplate = Velocity.getTemplate("vms/web/columns.vm", "UTF-8");
FileWriter columnsTemplateFileWriter = new FileWriter(vuePath + lowercaseName + "\\utils\\columns.ts");
columnsTemplate.merge(context, columnsTemplateFileWriter);
columnsTemplateFileWriter.close();
// 写入controller模板
Template controllerTemplate = Velocity.getTemplate("vms/server/controller.vm", "UTF-8");
FileWriter controllerTemplateFileWriter = new FileWriter(controllerPath + originalName + "Controller.java");
controllerTemplate.merge(context, controllerTemplateFileWriter);
controllerTemplateFileWriter.close();
// 写入servicePath模板
Template servicePathTemplate = Velocity.getTemplate("vms/server/service.vm", "UTF-8");
FileWriter servicePathTemplateFileWriter = new FileWriter(servicePath + originalName + "Service.java");
servicePathTemplate.merge(context, servicePathTemplateFileWriter);
servicePathTemplateFileWriter.close();
// 写入serviceImplPath模板
Template serviceImplPathTemplate = Velocity.getTemplate("vms/server/serviceImpl.vm", "UTF-8");
FileWriter serviceImplPathTemplateFileWriter = new FileWriter(serviceImplPath + originalName + "ServiceImpl.java");
serviceImplPathTemplate.merge(context, serviceImplPathTemplateFileWriter);
serviceImplPathTemplateFileWriter.close();
// 写入serviceImplPath模板
Template mapperPathTemplate = Velocity.getTemplate("vms/server/mapper.vm", "UTF-8");
FileWriter mapperPathTemplateFileWriter = new FileWriter(mapperPath + originalName + "Mapper.java");
mapperPathTemplate.merge(context, mapperPathTemplateFileWriter);
mapperPathTemplateFileWriter.close();
// 写入resourceMapperPath模板
Template resourceMapperPathTemplate = Velocity.getTemplate("vms/server/resourceMapper.vm", "UTF-8");
FileWriter resourceMapperPathTemplateFileWriter = new FileWriter(resourceMapperPath + originalName + "Mapper.xml");
resourceMapperPathTemplate.merge(context, resourceMapperPathTemplateFileWriter);
resourceMapperPathTemplateFileWriter.close();
}
}

View File

@ -0,0 +1,98 @@
package cn.bunny.common.generator.utils;
import cn.bunny.common.generator.entity.BaseField;
import cn.bunny.common.generator.entity.StoreTypeField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.lang.reflect.Field;
import java.util.*;
import static cn.bunny.common.generator.generator.WebGeneratorCode.commonPath;
public class GeneratorCodeUtils {
public static String convertJavaTypeToTypeScript(Class<?> javaType) {
if (javaType.isPrimitive()) {
return getPrimitiveTypeMapping(javaType.getSimpleName());
} else if (javaType.isArray()) {
return "Array<" + convertJavaTypeToTypeScript(javaType.getComponentType()) + ">";
} else if (javaType.equals(List.class) || javaType.equals(Set.class) || javaType.equals(Map.class)) {
return "any";
} else {
return getPrimitiveTypeMapping(javaType.getSimpleName());
}
}
/**
* * 将Java类型转成JS/TS类型
*
* @param primitiveType Java的类型
* @return 转换后的类型
*/
private static String getPrimitiveTypeMapping(String primitiveType) {
return switch (primitiveType) {
case "int", "long", "short", "byte", "float", "double", "Byte", "Integer", "Long", "Float", "Double" ->
"number";
case "boolean", "Boolean" -> "boolean";
case "char", "Character", "String" -> "string";
default -> "any";
};
}
/**
* * 生成返回对象类字段
* 返回字段包含TS接口名称TS接口名称注释TS接口字段TS字段类型TS字段注释TS字段注释解释
*
* @param voClass 返回对象类
* @return 整理好返回字段
*/
public static StoreTypeField handleGenerator(Class<?> voClass) {
// 类的详细信息
String interfaceAnnotation = voClass.getAnnotation(ApiModel.class).description();
// 字段
List<Field> fields = new ArrayList<>(Arrays.stream(voClass.getDeclaredFields()).toList());
List<Field> superList = Arrays.stream(voClass.getSuperclass().getDeclaredFields()).toList();
fields.addAll(superList);
List<BaseField> list = fields.stream()
.map(field -> {
field.setAccessible(true);
// 将类型转成TS
Class<?> type = field.getType();
String convertJavaTypeToTypeScript = GeneratorCodeUtils.convertJavaTypeToTypeScript(type);
// 注释内容
String annotationName = Objects.requireNonNull(field.getAnnotation(ApiModelProperty.class).name());
// 注释解释
String value = field.getAnnotation(ApiModelProperty.class).value();
// 构建返回内容
BaseField storeTypeField = new BaseField();
storeTypeField.setName(field.getName());
storeTypeField.setAnnotation(annotationName);
storeTypeField.setType(convertJavaTypeToTypeScript);
return storeTypeField;
}).toList();
StoreTypeField storeTypeField = new StoreTypeField();
storeTypeField.setBaseFieldList(list);
storeTypeField.setInterfaceName(voClass.getSimpleName());
storeTypeField.setInterfaceAnnotation(interfaceAnnotation);
return storeTypeField;
}
/**
* * 通用处理路径内容
*
* @param path 路径
* @return 处理好的路径
*/
public static String ReplacePathHandle(String path) {
return "@" + path.replace(commonPath, "").replace("\\", "/");
}
}

View File

@ -0,0 +1,65 @@
package cn.bunny.services.controller;
import cn.bunny.dao.pojo.result.Result;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import cn.bunny.dao.pojo.result.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* <p>
* ${classTitle}表 前端控制器
* </p>
*
* @author Bunny
* @since ${date}
*/
@Tag(name = "${classTitle}", description = "${classTitle}相关接口")
@RestController
@RequestMapping("admin/${lowercaseName}")
public class ${originalName}Controller {
@Autowired
private ${originalName}Service ${lowercaseName}Service;
@Operation(summary = "分页查询${classTitle}", description = "分页查询${classTitle}")
@GetMapping("get${originalName}List/{page}/{limit}")
public Mono<Result<PageResult<${originalName}Vo>>> get${originalName}List(
@Parameter(name = "page", description = "当前页", required = true)
@PathVariable("page") Integer page,
@Parameter(name = "limit", description = "每页记录数", required = true)
@PathVariable("limit") Integer limit,
${originalName}Dto dto) {
Page<${originalName}> pageParams = new Page<>(page, limit);
PageResult<${originalName}Vo> pageResult = ${lowercaseName}Service.get${originalName}List(pageParams, dto);
return Mono.just(Result.success(pageResult));
}
@Operation(summary = "添加${classTitle}", description = "添加${classTitle}")
@PostMapping("add${originalName}")
public Mono<Result<String>> add${originalName}(@Valid @RequestBody ${originalName}AddDto dto) {
${lowercaseName}Service.add${originalName}(dto);
return Mono.just(Result.success(ResultCodeEnum.ADD_SUCCESS));
}
@Operation(summary = "更新${classTitle}", description = "更新${classTitle}")
@PutMapping("update${originalName}")
public Mono<Result<String>> update${originalName}(@Valid @RequestBody ${originalName}UpdateDto dto) {
${lowercaseName}Service.update${originalName}(dto);
return Mono.just(Result.success(ResultCodeEnum.UPDATE_SUCCESS));
}
@Operation(summary = "删除${classTitle}", description = "删除${classTitle}")
@DeleteMapping("delete${originalName}")
public Mono<Result<String>> delete${originalName}(@RequestBody List<Long> ids) {
${lowercaseName}Service.delete${originalName}(ids);
return Mono.just(Result.success(ResultCodeEnum.DELETE_SUCCESS));
}
}

View File

@ -0,0 +1,35 @@
package cn.bunny.services.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* ${classTitle} Mapper 接口
* </p>
*
* @author Bunny
* @since ${date}
*/
@Mapper
public interface ${originalName}Mapper extends BaseMapper<${originalName}> {
/**
* * 分页查询${classTitle}内容
*
* @param pageParams ${classTitle}分页参数
* @param dto ${classTitle}查询表单
* @return ${classTitle}分页结果
*/
IPage<${originalName}Vo> selectListByPage(@Param("page") Page<${originalName}> pageParams, @Param("dto") ${originalName}Dto dto);
/**
* 物理删除${classTitle}
*
* @param ids 删除 id 列表
*/
void deleteBatchIdsWithPhysics(List<Long> ids);
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bunny.services.mapper.${originalName}Mapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="$type">
#foreach($field in $baseResultMaps)
<id column="$field.column" property="$field.property"/>
#end
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
$baseColumnList
</sql>
<!-- 分页查询${classTitle}内容 -->
<select id="selectListByPage" resultType="${voClassType}">
select
base.*,
create_user.username as create_username,
update_user.username as update_username
from $tableName base
left join sys_user create_user on create_user.id = base.create_user
left join sys_user update_user on update_user.id = base.update_user
<where>
base.is_deleted = 0
#foreach($field in $pageQueryMap)
<if test="dto.${field.property} != null and dto.${field.property} != ''">
and base.$field.column like CONCAT('%',#{dto.${field.property}},'%')
</if>
#end
</where>
</select>
<!-- 物理删除${classTitle} -->
<delete id="deleteBatchIdsWithPhysics">
delete
from $tableName
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,49 @@
package cn.bunny.services.service;
import cn.bunny.dao.entity.system.MenuIcon;
import cn.bunny.dao.pojo.result.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
/**
* <p>
* ${classTitle} 服务类
* </p>
*
* @author Bunny
* @since ${date}
*/
public interface ${originalName}Service extends IService<${originalName}> {
/**
* * 获取${classTitle}列表
*
* @return ${classTitle}返回列表
*/
PageResult<${originalName}Vo> get${originalName}List(Page<${originalName}> pageParams, ${originalName}Dto dto);
/**
* * 添加${classTitle}
*
* @param dto 添加表单
*/
void add${originalName}(@Valid ${originalName}AddDto dto);
/**
* * 更新${classTitle}
*
* @param dto 更新表单
*/
void update${originalName}(@Valid ${originalName}UpdateDto dto);
/**
* * 删除|批量删除${classTitle}类型
*
* @param ids 删除id列表
*/
void delete${originalName}(List<Long> ids);
}

View File

@ -0,0 +1,78 @@
package cn.bunny.services.service.impl;
import cn.bunny.dao.pojo.result.PageResult;
import cn.bunny.services.mapper.${originalName}Mapper;
import cn.bunny.services.service.${originalName}Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* ${classTitle} 服务实现类
* </p>
*
* @author Bunny
* @since ${date}
*/
@Service
public class ${originalName}ServiceImpl extends ServiceImpl<${originalName}Mapper, ${originalName}> implements ${originalName}Service {
/**
* * ${classTitle} 服务实现类
*
* @param pageParams ${classTitle}分页查询page对象
* @param dto ${classTitle}分页查询对象
* @return 查询分页${classTitle}返回对象
*/
@Override
public PageResult<${originalName}Vo> get${originalName}List(Page<${originalName}> pageParams, ${originalName}Dto dto) {
IPage<${originalName}Vo> page = baseMapper.selectListByPage(pageParams, dto);
return PageResult.<${originalName}Vo>builder()
.list(page.getRecords())
.pageNo(page.getCurrent())
.pageSize(page.getSize())
.total(page.getTotal())
.build();
}
/**
* 添加${classTitle}
*
* @param dto ${classTitle}添加
*/
@Override
public void add${originalName}(@Valid ${originalName}AddDto dto) {
// 保存数据
${originalName} ${lowercaseName} = new ${originalName}();
BeanUtils.copyProperties(dto, ${lowercaseName});
save(${lowercaseName});
}
/**
* 更新${classTitle}
*
* @param dto ${classTitle}更新
*/
@Override
public void update${originalName}(@Valid ${originalName}UpdateDto dto) {
// 更新内容
${originalName} ${lowercaseName} = new ${originalName}();
BeanUtils.copyProperties(dto, ${lowercaseName});
updateById(${lowercaseName});
}
/**
* 删除|批量删除${classTitle}
*
* @param ids 删除id列表
*/
@Override
public void delete${originalName}(List<Long> ids) {
baseMapper.deleteBatchIdsWithPhysics(ids);
}
}

View File

@ -0,0 +1,22 @@
import { http } from '@/api/service/request';
import type { BaseResult, ResultTable } from '@/api/service/types';
/** ${classDescription}---获取${classDescription}列表 */
export const fetchGet${originalName}List = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `${lowercaseName}/get${originalName}List/${data.currentPage}/${data.pageSize}`, { params: data });
};
/** ${classDescription}---添加${classDescription} */
export const fetchAdd${originalName} = (data: any) => {
return http.request<BaseResult<object>>('post', '${lowercaseName}/add${originalName}', { data });
};
/** ${classDescription}---更新${classDescription} */
export const fetchUpdate${originalName} = (data: any) => {
return http.request<BaseResult<object>>('put', '${lowercaseName}/update${originalName}', { data });
};
/** ${classDescription}---删除${classDescription} */
export const fetchDelete${originalName} = (data: any) => {
return http.request<BaseResult<object>>('delete', '${lowercaseName}/delete${originalName}', { data });
};

View File

@ -0,0 +1,27 @@
import { reactive, ref } from 'vue';
import { $t } from '@/plugins/i18n';
import type { FormRules } from 'element-plus';
// 表格列
export const columns: TableColumnList = [
{ type: 'selection', align: 'left' },
{ type: 'index', index: (index: number) => index + 1, label: '序号', width: 60 },
#foreach($field in $baseFieldList)
// $field.annotation
{ label: $t('$field.name'), prop: '$field.name' },
#end
{ label: $t('table.updateTime'), prop: 'updateTime', sortable: true, width: 160 },
{ label: $t('table.createTime'), prop: 'createTime', sortable: true, width: 160 },
{ label: $t('table.createUser'), prop: 'createUser', slot: 'createUser', width: 130 },
{ label: $t('table.updateUser'), prop: 'updateUser', slot: 'updateUser', width: 130 },
{ label: $t('table.operation'), fixed: 'right', width: 210, slot: 'operation' },
];
// 添加规则
export const rules = reactive
<FormRules>({
#foreach($field in $baseFieldList)
// $field.annotation
$field.name: [{ required: true, message: `$leftBrace$t('input')}$leftBrace$t('${field.name}')}`, trigger: 'blur' }],
#end
});

View File

@ -0,0 +1,36 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { FormInstance } from 'element-plus';
import { rules } from '${columnsPath}';
import { FormProps } from '${typesPath}';
import { frameSureOptions } from '@/enums';
import { $t } from '@/plugins/i18n';
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
#foreach($item in $baseFieldList)
#if(${item.name})
// $!{item.annotation}
${item.name}: undefined,
#end
#end
}),
});
const formRef = ref<FormInstance>();
const form = ref(props.formInline);
defineExpose({ formRef });
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
#foreach($item in $baseFieldList)
<!-- $item.annotation -->
<el-form-item :label="$t('${item.name}')" prop="$item.name">
<el-input v-model="form.$item.name" autocomplete="off" type="text" :placeholder="$t('input') + $t('${item.name}')" />
</el-form-item>
#end
</el-form>
</template>

View File

@ -0,0 +1,130 @@
import { deviceDetection } from '@pureadmin/utils';
import { addDialog } from '@/components/BaseDialog/index';
import ${originalName}Dialog from '${dialogPath}';
import { use${originalName}Store } from '${storePath}';
import { h, ref } from 'vue';
import { messageBox } from '@/utils/message';
import type { FormItemProps } from '${typesPath}';
import { $t } from '@/plugins/i18n';
import { message, messageBox } from "@/utils/message";
import DeleteBatchDialog from "@/components/Table/DeleteBatchDialog.vue";
export const formRef = ref();
// 删除ids
export const deleteIds = ref([]);
const ${lowercaseName}Store = use${originalName}Store();
/** 搜索初始化${classTitle} */
export async function onSearch() {
${lowercaseName}Store.loading = true;
await ${lowercaseName}Store.get${originalName}List();
${lowercaseName}Store.loading = false;
}
/** 添加${classTitle} */
export function onAdd() {
addDialog({
title: `$leftBrace $t("addNew")}$leftBrace$t("${lowercaseName}")}`,
width: '30%',
props: {
formInline: {
#foreach($item in $addFormList)
$!{item}: undefined,
#end
},
},
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(${originalName}Dialog, { ref: formRef }),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
if (!valid) return;
const result = await ${lowercaseName}Store.add${originalName}(form);
if (!result) return;
done();
await onSearch();
});
},
});
}
/** 更新${classTitle} */
export function onUpdate(row: any) {
addDialog({
title: `$leftBrace$t("modify")}$leftBrace$t("${lowercaseName}")}`,
width: '30%',
props: {
formInline: {
#foreach($item in $addFormList)
$!{item}: row.$!{item},
#end
}
},
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(${originalName}Dialog, { ref: formRef }),
beforeSure: (done, { options }) => {
const form = options.props.formInline as FormItemProps;
formRef.value.formRef.validate(async (valid: any) => {
if (!valid) return;
const result = await ${lowercaseName}Store.update${originalName}({ ...form, id: row.id });
if (!result) return;
done();
await onSearch();
});
},
});
}
/** 删除${classTitle} */
export const onDelete = async (row: any) => {
const id = row.id;
// 是否确认删除
const result = await messageBox({
title: $t('confirmDelete'),
showMessage: false,
confirmMessage: undefined,
cancelMessage: $t("cancel_delete"),
});
if (!result) return;
// 删除数据
await ${lowercaseName}Store.delete${originalName}([id]);
await onSearch();
};
/** 批量删除 */
export const onDeleteBatch = async () => {
const ids = deleteIds.value;
const formDeletedBatchRef = ref();
addDialog({
title: $t('deleteBatchTip'),
width: '30%',
props: { formInline: { confirmText: '' } },
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(DeleteBatchDialog, { ref: formDeletedBatchRef }),
beforeSure: (done, { options }) => {
formDeletedBatchRef.value.formDeletedBatchRef.validate(async (valid: any) => {
if (!valid) return;
const text = options.props.formInline.confirmText.toLowerCase();
if (text === 'yes' || text === 'y') {
// 删除数据
await ${lowercaseName}Store.delete${originalName}(ids);
await onSearch();
done();
} else message($t('deleteBatchTip'), { type: 'warning' });
});
},
});
};

View File

@ -0,0 +1,121 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {deleteIds, onSearch} from '';
import {FormInstance} from "element-plus";
const tableRef = ref();
const formRef = ref();
const ${lowercaseName}Store = use${originalName}Store();
/** 当前页改变时 */
const onCurrentPageChange = async (value: number) => {
${lowercaseName}Store.pagination.currentPage = value;
await onSearch();
};
/** 当分页发生变化 */
const onPageSizeChange = async (value: number) => {
${lowercaseName}Store.pagination.pageSize = value;
await onSearch();
};
/** 选择多行 */
const onSelectionChange = (rows: Array<any>) => {
deleteIds.value = rows.map((row: any) => row.id);
};
/** 重置表单 */
const resetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
await onSearch();
};
onMounted(() => {
onSearch();
});
</script>
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="${lowercaseName}Store.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
#foreach($item in $formList)
<!-- $item.annotation -->
<el-form-item :label="$t('${item.name}')" prop="${item.name}">
<el-input v-model="${lowercaseName}Store.form.${item.name}" :placeholder="`$leftBrace$t('input')}$leftBrace$t('${item.name}')}`"
class="!w-[180px]" clearable/>
</el-form-item>
#end
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="${lowercaseName}Store.loading" type="primary" @click="onSearch"> {{ $t('search')
}}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item>
</el-form>
<PureTableBar :columns="columns" title="${classDescription}" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> {{ $t('addNew') }}</el-button>
<!-- 批量删除按钮 -->
<el-button v-show="deleteIds.length > 0" :icon="useRenderIcon(Delete)" type="danger" @click="onDeleteBatch">
{{ $t('delete_batches') }}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 96 }"
:columns="dynamicColumns"
:data="${lowercaseName}Store.datalist"
:header-cell-style="{ background: 'var(--el-fill-color-light)', color: 'var(--el-text-color-primary)' }"
:loading="${lowercaseName}Store.loading"
:size="size"
adaptive
align-whole="center"
border
highlight-current-row
row-key="id"
showOverflowTooltip
table-layout="auto"
:pagination="${lowercaseName}Store.pagination"
@page-size-change="onPageSizeChange"
@selection-change="onSelectionChange"
@page-current-change="onCurrentPageChange"
>
<template #createUser="{ row }">
<el-button v-show="row.createUser" link type="primary" @click="selectUserinfo(row.createUser)">
{{ row.createUsername }}
</el-button>
</template>
<template #updateUser="{ row }">
<el-button v-show="row.updateUser" link type="primary" @click="selectUserinfo(row.updateUser)">
{{ row.updateUsername }}
</el-button>
</template>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify')
}}
</el-button>
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd"> {{ $t('addNew') }}
</el-button>
<!-- TODO 待完成 -->
<el-popconfirm :title="`${leftBrace}$t('delete')}${row.email}?`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>

View File

@ -0,0 +1,69 @@
import { defineStore } from 'pinia';
import { fetchAdd${originalName}, fetchDelete${originalName}, fetchGet${originalName}List, fetchUpdate${originalName} } from '${apiPath}';
import { pageSizes } from '@/enums/baseConstant';
import { storeMessage } from '@/utils/message';
import { storePagination } from '@/store/useStorePagination';
/**
* ${classTitle} Store
*/
export const use${originalName}Store = defineStore('${lowercaseName}Store', {
state() {
return {
// ${classTitle}列表
datalist: [],
// 查询表单
form: {
#foreach($item in $formList)
// $!{item.annotation}
$!{item.name}: undefined,
#end
},
// 分页查询结果
pagination: {
currentPage: 1,
pageSize: 30,
total: 1,
pageSizes,
},
// 加载
loading: false,
};
},
getters: {},
actions: {
/** 获取${classTitle} */
async get${originalName}List() {
// 整理请求参数
const data = { ...this.pagination, ...this.form };
delete data.pageSizes;
delete data.total;
delete data.background;
// 获取${classTitle}列表
const result = await fetchGet${originalName}List(data);
// 公共页面函数hook
const pagination = storePagination.bind(this);
return pagination(result);
},
/** 添加${classTitle} */
async add${originalName}(data: any) {
const result = await fetchAdd${originalName}(data);
return storeMessage(result);
},
/** 修改${classTitle} */
async update${originalName}(data: any) {
const result = await fetchUpdate${originalName}(data);
return storeMessage(result);
},
/** 删除${classTitle} */
async delete${originalName}(data: any) {
const result = await fetchDelete${originalName}(data);
return storeMessage(result);
},
},
});

View File

@ -0,0 +1,12 @@
// 添加或者修改表单元素
export interface FormItemProps {
#foreach($field in $baseFieldList)
// $field.annotation
$field.name: $field.type;
#end
}
// 添加或修改表单Props
export interface FormProps {
formInline: FormItemProps;
}

53
common/pom.xml Normal file
View File

@ -0,0 +1,53 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>financial-admin-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<name>common Maven Webapp</name>
<url>https://maven.apache.org</url>
<modules>
<module>service-utils</module>
<module>common-generator</module>
</modules>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mysql连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mysql连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,70 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>service-utils</artifactId>
<packaging>jar</packaging>
<name>service-utils</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 解决 javax.xml.bind 错误 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redisson 分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.26.1</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<!-- 查询ip地址 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
<!-- WebSocket -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</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>
</dependencies>
</project>

View File

@ -0,0 +1,46 @@
package cn.bunny.common.service.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.io.IOException;
@ControllerAdvice
public class ControllerStringParamTrimConfig {
/**
* 创建 String trim 编辑器
* 构造方法中 boolean 参数含义为如果是空白字符串,是否转换为null
* 即如果为true,那么 " " 会被转换为 null,否者为 ""
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
StringTrimmerEditor propertyEditor = new StringTrimmerEditor(false);
// String 类对象注册编辑器
binder.registerCustomEditor(String.class, propertyEditor);
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return jacksonObjectMapperBuilder -> {
// String 类型自定义反序列化操作
jacksonObjectMapperBuilder
.deserializerByType(String.class, new StdScalarDeserializer<String>(String.class) {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
// 去除全部空格
// return StringUtils.trimAllWhitespace(jsonParser.getValueAsString());
// 仅去除前后空格
return jsonParser.getValueAsString().trim();
}
});
};
}
}

View File

@ -0,0 +1,33 @@
package cn.bunny.common.service.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class Knife4jConfig {
@Bean
public OpenAPI openAPI() {
// 作者等信息
Contact contact = new Contact().name("Bunny").email("1319900154@qq.com").url("http://z-bunny.cn");
// 使用协议
License license = new License().name("MIT").url("https://MUT.com");
// 相关信息
Info info = new Info().title("Bunny-Admin").description("权限管理模板").version("v1.0.0").contact(contact).license(license).termsOfService("MIT");
return new OpenAPI().info(info).externalDocs(new ExternalDocumentation());
}
// 管理员相关分类接口
@Bean
public GroupedOpenApi groupedOpenAdminApi() {
return GroupedOpenApi.builder().group("默认请求接口").pathsToMatch("/admin/**").build();
}
}

View File

@ -0,0 +1,41 @@
package cn.bunny.common.service.config;
import cn.bunny.common.service.context.BaseContext;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 配置MP在修改和新增时的操作
*/
@Component
public class MyBatisPlusFieldConfig implements MetaObjectHandler {
/**
* 使用mp做添加操作时候这个方法执行
*/
@Override
public void insertFill(MetaObject metaObject) {
// 设置属性值
this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
if (BaseContext.getUsername() != null) {
this.setFieldValByName("createUser", BaseContext.getUserId(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getUserId(), metaObject);
}
}
/**
* 使用mp做修改操作时候这个方法执行
*/
@Override
public void updateFill(MetaObject metaObject) {
if (BaseContext.getUserId() != null) {
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateUser", BaseContext.getUserId(), metaObject);
}
}
}

View File

@ -0,0 +1,36 @@
package cn.bunny.common.service.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Mybatis-Plus配置类
*/
@EnableTransactionManagement
@Configuration
@Slf4j
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置最大分页为100
paginationInnerInterceptor.setMaxLimit(600L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}

View File

@ -0,0 +1,141 @@
package cn.bunny.common.service.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 设置Redis序列化
*/
@Component
@Slf4j
public class RedisConfiguration {
/**
* 使用StringRedisSerializer序列化为字符串
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 设置key序列化为string
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置value序列化为JSON使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
// 开启Redis事务
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
/**
* * 配置Redis过期时间
* 一个月
* 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题
*/
@Bean
@Primary
@SuppressWarnings("all")
public CacheManager cacheManagerWithMouth(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofDays(30));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置redis过期时间
* 半个月
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithHalfOfMouth(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofDays(15));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* * 配置Redis过期时间1小时
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithHours(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofHours(1));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
/**
* 指定的日期模式
*/
public Jackson2JsonRedisSerializer<Object> jsonRedisSerializer() {
// LocalDatetime序列化默认不兼容jdk8日期序列化
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 对象序列化
ObjectMapper mapper = new ObjectMapper();
// 设置ObjectMapper访问权限
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 记录序列化之后的数据类型方便反序列化
// mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.EVERYTHING);
// 关闭默认的日期格式化方式默认UTC日期格式 yyyy-MM-ddTHH:mm:ss.SSS
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(timeModule);
return new Jackson2JsonRedisSerializer<>(mapper, Object.class);
}
}

View File

@ -0,0 +1,40 @@
package cn.bunny.common.service.context;
import cn.bunny.dao.vo.system.user.LoginVo;
public class BaseContext {
private static final ThreadLocal<Long> userId = new ThreadLocal<>();
private static final ThreadLocal<String> username = new ThreadLocal<>();
private static final ThreadLocal<LoginVo> loginVo = new ThreadLocal<>();
// 用户id相关
public static Long getUserId() {
return userId.get();
}
public static void setUserId(Long _userId) {
userId.set(_userId);
}
public static String getUsername() {
return username.get();
}
public static void setUsername(String _username) {
username.set(_username);
}
public static LoginVo getLoginVo() {
return loginVo.get();
}
public static void setLoginVo(LoginVo _loginVo) {
loginVo.set(_loginVo);
}
public static void removeUser() {
username.remove();
userId.remove();
loginVo.remove();
}
}

View File

@ -0,0 +1,33 @@
package cn.bunny.common.service.exception;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@NoArgsConstructor
@Getter
@ToString
@Slf4j
public class BunnyException extends RuntimeException {
Integer code;// 状态码
String message;// 描述信息
public BunnyException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BunnyException(String message) {
super(message);
this.message = message;
}
public BunnyException(ResultCodeEnum codeEnum) {
super(codeEnum.getMessage());
this.code = codeEnum.getCode();
this.message = codeEnum.getMessage();
}
}

View File

@ -0,0 +1,130 @@
package cn.bunny.common.service.exception;
import cn.bunny.common.service.context.BaseContext;
import cn.bunny.dao.pojo.result.Result;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.nio.file.AccessDeniedException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 自定义异常信息
@ExceptionHandler(BunnyException.class)
@ResponseBody
public Result<Object> exceptionHandler(BunnyException exception) {
Integer code = exception.getCode() != null ? exception.getCode() : 500;
return Result.error(null, code, exception.getMessage());
}
// 运行时异常信息
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Result<Object> exceptionHandler(RuntimeException exception) {
String message = exception.getMessage();
// 解析异常
String jsonParseError = "JSON parse error (.*)";
Matcher jsonParseErrorMatcher = Pattern.compile(jsonParseError).matcher(message);
if (jsonParseErrorMatcher.find()) {
return Result.error(null, 500, "JSON解析异常 " + jsonParseErrorMatcher.group(1));
}
// 数据过大
String dataTooLongError = "Data too long for column (.*?) at row 1";
Matcher dataTooLongErrorMatcher = Pattern.compile(dataTooLongError).matcher(message);
if (dataTooLongErrorMatcher.find()) {
return Result.error(null, 500, dataTooLongErrorMatcher.group(1) + " 字段数据过大");
}
// 主键冲突
String primaryKeyError = "Duplicate entry '(.*?)' for key .*";
Matcher primaryKeyErrorMatcher = Pattern.compile(primaryKeyError).matcher(message);
if (primaryKeyErrorMatcher.find()) {
return Result.error(null, 500, "[" + primaryKeyErrorMatcher.group(1) + "]已存在");
}
log.error("GlobalExceptionHandler===>运行时异常信息:{}", message);
exception.printStackTrace();
return Result.error(null, 500, "服务器异常");
}
// 捕获系统异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result<Object> error(Exception exception) {
log.error("捕获系统异常:{}", exception.getMessage());
log.error("GlobalExceptionHandler===>系统异常信息:{}", (Object) exception.getStackTrace());
// 错误消息
String message = exception.getMessage();
// 匹配到内容
String patternString = "Request method '(\\w+)' is not supported";
Matcher matcher = Pattern.compile(patternString).matcher(message);
if (matcher.find()) return Result.error(null, 500, "请求方法错误,不是 " + matcher.group(1) + "类型请求");
// 请求API不存在
String noStaticResource = "No static resource (.*)\\.";
Matcher noStaticResourceMatcher = Pattern.compile(noStaticResource).matcher(message);
if (noStaticResourceMatcher.find())
return Result.error(null, 500, "请求API不存在 " + noStaticResourceMatcher.group(1));
// 返回错误内容
return Result.error(null, 500, "系统异常");
}
// 表单验证字段
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
log.error("表单验证失败用户Id{}", BaseContext.getUserId());
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(null, 201, errorMessage);
}
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result<Object> error(ArithmeticException exception) {
log.error("GlobalExceptionHandler===>特定异常信息:{}", exception.getMessage());
return Result.error(null, 500, exception.getMessage());
}
// spring security异常
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result<String> error(AccessDeniedException exception) throws AccessDeniedException {
log.error("GlobalExceptionHandler===>spring security异常{}", exception.getMessage());
return Result.error(ResultCodeEnum.SERVICE_ERROR);
}
// 处理SQL异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
@ResponseBody
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {
log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage());
String message = exception.getMessage();
if (message.contains("Duplicate entry")) {
// 错误信息
return Result.error(ResultCodeEnum.USER_IS_EMPTY);
} else {
return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION);
}
}
}

View File

@ -0,0 +1,21 @@
package cn.bunny.common.service.utils;
import cn.bunny.common.service.exception.BunnyException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
@Slf4j
public class EmptyUtil {
/**
* 是否为空
*
* @param value 判断值
* @param message 错误消息
*/
public static void isEmpty(Object value, String message) {
if (value == null || !StringUtils.hasText(value.toString())) {
log.error("为空对象错误:{}{}", value, message);
throw new BunnyException(message);
}
}
}

View File

@ -0,0 +1,31 @@
package cn.bunny.common.service.utils;
import org.springframework.stereotype.Component;
@Component
public class FileUtil {
/**
* * 获取文件大小字符串
*/
public static String getSize(Long fileSize) {
double fileSizeInKB = fileSize / 1024.00;
double fileSizeInMB = fileSizeInKB / 1024;
double fileSizeInGB = fileSizeInMB / 1024;
String size;
if (fileSizeInGB >= 1) {
fileSizeInGB = Double.parseDouble(String.format("%.2f", fileSizeInGB));
size = fileSizeInGB + "GB";
} else if (fileSizeInMB >= 1) {
fileSizeInMB = Double.parseDouble(String.format("%.2f", fileSizeInMB));
size = fileSizeInMB + "MB";
} else if (fileSizeInKB >= 1) {
fileSizeInKB = Double.parseDouble(String.format("%.2f", fileSizeInKB));
size = fileSizeInKB + "KB";
} else {
size = fileSize + "B";
}
return size;
}
}

View File

@ -0,0 +1,206 @@
package cn.bunny.common.service.utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtil {
public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}
return httpClient.execute(request);
}
public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}
return httpClient.execute(request);
}
public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception {
HttpClient httpClient = wrapClient(host);
HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}
return httpClient.execute(request);
}
private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (!sbQuery.isEmpty()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), StandardCharsets.UTF_8));
}
}
}
if (!sbQuery.isEmpty()) {
sbUrl.append("?").append(sbQuery);
}
}
return sbUrl.toString();
}
private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}
return httpClient;
}
private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
public void checkServerTrusted(X509Certificate[] xcs, String str) {
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (Exception ex) {
throw new RuntimeException();
}
}
}

View File

@ -0,0 +1,352 @@
package cn.bunny.common.service.utils;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import io.jsonwebtoken.*;
import io.micrometer.common.lang.Nullable;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtHelper {
// 时间 按天 计算
private static final long tokenExpiration = 24 * 60 * 60 * 1000;
// JWT 秘钥
private static final String tokenSignKey = "Bunny-Java-Template";
// 默认主题
private static final String subject = "Bunny";
// 默认时间
private static final Date time = new Date(System.currentTimeMillis() + tokenExpiration * 7);
/**
* 使用默认主题默认时间默认秘钥创建自定义集合token
*
* @param map 集合
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 使用默认主题默认秘钥自定义时间创建集合形式token
*
* @param map 集合
* @param time 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, Date time) {
return Jwts.builder()
.setSubject(subject)
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.setExpiration(time)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 使用默认主题默认秘钥自定义时间创建集合形式token
*
* @param map 集合
* @param day 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, Integer day) {
return Jwts.builder()
.setSubject(subject)
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration * day))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 使用默认主题默认秘钥自定义key创建集合形式token
*
* @param map 集合
* @param tokenSignKey 自定义key
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String tokenSignKey) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 使用自定义主题自定义时间创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param time 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String subject, Date time) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param tokenSignKey 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String subject, String tokenSignKey) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact();
}
/**
* 创建集合形式token
*
* @param map 集合
* @param tokenSignKey 主题
* @param time 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String tokenSignKey, Integer time) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration * time))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param day 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String subject, String tokenSignKey, Integer day) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration * day))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param time 过期时间
* @return token
*/
public static String createTokenWithMap(Map<String, Object> map, String subject, String tokenSignKey, Date time) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
}
/**
* 根据用户名和ID创建token
*
* @param userId 用户ID
* @param username 用户名
* @param day 过期时间
* @return token值
*/
public static String createToken(Long userId, String username, Integer day) {
return Jwts.builder()
.setSubject(subject)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration * day))
.claim("userId", userId)
.claim("username", username)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
}
/**
* 使用token获取map集合,使用默认秘钥
*
* @param token token
* @return map集合
*/
public static Map<String, Object> getMapByToken(String token) {
try {
if (!StringUtils.hasText(token)) throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
// body 值转为map
return new HashMap<>(claims);
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
}
}
/**
* 使用token获取map集合
*
* @param token token
* @param signKey 秘钥
* @return map集合
*/
public static Map<String, Object> getMapByToken(String token, String signKey) {
try {
if (!StringUtils.hasText(token)) throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
// body 值转为map
return new HashMap<>(body);
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
}
}
/**
* 根据token获取主题
*
* @param token token
* @return 主题
*/
public static String getSubjectByToken(String token) {
return getSubjectByTokenHandler(token, tokenSignKey);
}
@Nullable
private static String getSubjectByTokenHandler(String token, String tokenSignKey) {
try {
if (!StringUtils.hasText(token)) throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return body.getSubject();
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
}
}
/**
* 根据token获取主题
*
* @param token token
* @return 主题
*/
public static String getSubjectByToken(String token, String tokenSignKey) {
return getSubjectByTokenHandler(token, tokenSignKey);
}
/**
* 根据token获取用户ID
*
* @param token token
* @return 用户ID
*/
public static Long getUserId(String token) {
try {
if (!StringUtils.hasText(token)) throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return Long.valueOf(String.valueOf(claims.get("userId")));
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
}
}
/**
* 根据token获取用户名
*
* @param token token
* @return 用户名
*/
public static String getUsername(String token) {
try {
if (!StringUtils.hasText(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("username");
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.TOKEN_PARSING_FAILED);
}
}
/**
* 判断token是否过期
*
* @param token token
* @return 是否过期
*/
public static boolean isExpired(String token) {
return isExpiredUtil(token, tokenSignKey);
}
/**
* 判断token是否过期
*
* @param token token
* @return 是否过期
*/
public static boolean isExpired(String token, String tokenSignKey) {
return isExpiredUtil(token, tokenSignKey);
}
/**
* 判断是否过期
*
* @param token token
* @param tokenSignKey key值
* @return 是否过期
*/
private static boolean isExpiredUtil(String token, String tokenSignKey) {
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Date expiration = claimsJws.getBody().getExpiration();
return expiration != null && expiration.before(new Date());
} catch (Exception exception) {
return true;
}
}
}

View File

@ -0,0 +1,12 @@
package cn.bunny.common.service.utils;
import cn.bunny.dao.pojo.result.Result;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import jakarta.servlet.http.HttpServletResponse;
public class ResponseHandlerUtil {
public static boolean loginAuthHandler(HttpServletResponse response, ResultCodeEnum loginAuth) {
ResponseUtil.out(response, Result.error(loginAuth));
return false;
}
}

View File

@ -0,0 +1,26 @@
package cn.bunny.common.service.utils;
import cn.bunny.dao.pojo.result.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import java.io.IOException;
public class ResponseUtil {
public static void out(HttpServletResponse response, Result<Object> result) {
ObjectMapper mapper = new ObjectMapper();
// 注册JavaTimeModule模块
mapper.registerModule(new JavaTimeModule());
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
try {
mapper.writeValue(response.getWriter(), result);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,21 @@
package cn.bunny.common.service.utils.ip;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel(value = "IpEntity对象", description = "用户IP相关信息")
public class IpEntity {
@ApiModelProperty("原始地址")
private String ipAddr;
@ApiModelProperty("IP归属地")
private String ipRegion;
}

View File

@ -0,0 +1,133 @@
package cn.bunny.common.service.utils.ip;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class IpUtil {
private static Searcher searcher;
/**
* 判断是否为合法 IP
*/
public static boolean checkIp(String ipAddress) {
String ip = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
Pattern pattern = Pattern.compile(ip);
Matcher matcher = pattern.matcher(ipAddress);
return matcher.matches();
}
/**
* 在服务启动时 ip2region 加载到内存中
*/
@PostConstruct
private static void initIp2Region() {
try {
InputStream inputStream = new ClassPathResource("/ipdb/ip2region.xdb").getInputStream();
byte[] bytes = FileCopyUtils.copyToByteArray(inputStream);
searcher = Searcher.newWithBuffer(bytes);
} catch (Exception exception) {
log.error("ip转换错误消息{}", exception.getMessage());
log.error("ip转换错误栈{}", (Object) exception.getStackTrace());
}
}
/**
* 获取 ip 所属地址
*
* @param ip ip
*/
public static String getIpRegion(String ip) {
if (ip.equals("0:0:0:0:0:0:0:1")) ip = "127.0.0.1";
boolean isIp = checkIp(ip);
if (isIp) {
initIp2Region();
try {
// searchIpInfo 的数据格式 国家|区域|省份|城市|ISP
String searchIpInfo = searcher.search(ip);
String[] splitIpInfo = searchIpInfo.split("\\|");
if (splitIpInfo.length > 0) {
if ("中国".equals(splitIpInfo[0])) {
// 国内属地返回省份和地区
return splitIpInfo[2] + "," + splitIpInfo[3] + " " + splitIpInfo[4];
} else if ("0".equals(splitIpInfo[0])) {
if ("内网IP".equals(splitIpInfo[4])) {
// 内网 IP
return splitIpInfo[4];
} else {
return "";
}
} else {
// 国外属地返回国家
return splitIpInfo[0];
}
}
} catch (Exception exception) {
log.error("获取 ip 所属地址消息:{}", exception.getMessage());
log.error("获取 ip 所属地址:{}", (Object) exception.getStackTrace());
}
return "";
} else {
throw new IllegalArgumentException("非法的IP地址");
}
}
/**
* * 获取当前用户登录IP地址
*
* @return IP地址
*/
public static IpEntity getCurrentUserIpAddress() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 判断IP地址归属地
String remoteAddr = requestAttributes != null ? getIpAddr(requestAttributes.getRequest()) : "0:0:0:0:0:0:0:1";
String ipRegion = IpUtil.getIpRegion(remoteAddr);
// 转成环回地址
remoteAddr = remoteAddr.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : remoteAddr;
return IpEntity.builder().ipAddr(remoteAddr).ipRegion(ipRegion).build();
}
/**
* * 获取IP地址
*
* @param request 请求头
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) ipAddress = InetAddress.getLocalHost().getHostAddress();
}
// 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = null;
}
return ipAddress;
}
}

View File

@ -0,0 +1,70 @@
package cn.bunny.common.service.utils.mail;
import cn.bunny.dao.pojo.common.EmailSend;
import cn.bunny.dao.pojo.common.EmailSendInit;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.web.multipart.MultipartFile;
import java.util.Objects;
public class MailSenderUtil {
/**
* * 邮件发送初始化
*
* @param emailSendInit 邮件发送初始化
*/
public static JavaMailSenderImpl senderUtil(EmailSendInit emailSendInit) {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(emailSendInit.getHost());
javaMailSender.setPort(emailSendInit.getPort());
javaMailSender.setUsername(emailSendInit.getUsername());
javaMailSender.setPassword(emailSendInit.getPassword());
javaMailSender.setProtocol(emailSendInit.getProtocol());
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
/**
* * 发送邮件
*
* @param emailSendInit 邮件发送初始化
* @param emailSend 邮件发送表单
*/
public static void sendEmail(EmailSendInit emailSendInit, EmailSend emailSend) throws MessagingException {
// 发送邮件初始化
JavaMailSenderImpl javaMailSender = senderUtil(emailSendInit);
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
// 设置发件人
helper.setFrom(emailSendInit.getUsername());
// 设置群发内容
helper.setTo(emailSend.getSendTo().toArray(new String[0]));
// 设置主题
helper.setSubject(emailSend.getSubject());
// 设置发送文本
helper.setText(emailSend.getText(), emailSend.isRichText());
// 设置抄送
helper.setCc(emailSend.getCcParam().toArray(new String[0]));
// 设置附件
MultipartFile[] files = emailSend.getFiles();
if (files != null) {
for (MultipartFile file : files) {
helper.addAttachment(Objects.requireNonNull(file.getOriginalFilename()), file);
}
}
// 发送邮件
javaMailSender.send(message);
}
}

View File

@ -0,0 +1,23 @@
Configuration example
mail:
host: smtp.qq.com # 邮箱地址
port: 465 # 邮箱端口号
username: xxx@qq.com # 设置发送邮箱
password: xx # 如果是纯数字要加引号
default-encoding: UTF-8 # 设置编码格式
protocol: smtps
properties:
mail:
debug: true # 是否开启debug模式发送邮件
smtp:
auth: true
connectionTimeout: 5000 # 设置连接延迟
timeout: 5000 # 延迟时间
writeTimeout: 5000 # 写入邮箱延迟
allow8BitMime: true
sendPartial: true
ssl:
enabled: true # 是否开启SSL连接
socketFactory:
class: javax.net.ssl.SSLSocketFactory # 必要设置!!!

View File

@ -0,0 +1,26 @@
package cn.bunny.common.service.utils.minio;
import io.minio.MinioClient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "bunny.minio")
@ConditionalOnProperty(name = "bunny.minio.bucket-name")// 当属性有值时这个配置才生效
@Data
@Slf4j
public class MinioProperties {
private String endpointUrl;
private String accessKey;
private String secretKey;
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
}
}

View File

@ -0,0 +1,176 @@
package cn.bunny.common.service.utils.minio;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.dao.pojo.common.MinioFilePath;
import cn.bunny.dao.pojo.constant.MinioConstant;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
/**
* Minio操作工具类 简化操作步骤
* ByBunny0212
*/
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioProperties properties;
@Autowired
private MinioClient minioClient;
/**
* 获取Minio文件路径
*/
public static MinioFilePath initUploadFileReturnMinioFilePath(String buckName, String minioPreType, MultipartFile file) {
String uuid = UUID.randomUUID().toString();
// 定义日期时间格式
LocalDateTime currentDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM-dd");
String extension = "";
// 原始文件名
String filename = file.getOriginalFilename();
if (StringUtils.hasText(filename) && filename.contains(".")) {
extension = "." + filename.substring(filename.lastIndexOf(".") + 1);
}
// UUID防止重名
String uuidFilename = uuid + extension;
// 拼接时间+UUID文件名
String timeUuidFilename = currentDateTime.format(formatter) + "/" + uuidFilename;// 加上时间路径
// 上传根文件夹+拼接时间+UUID文件名
String filepath = MinioConstant.getType(minioPreType) + timeUuidFilename;
// 桶名称+上传根文件夹+拼接时间+UUID文件名
String buckNameFilepath = "/" + buckName + MinioConstant.getType(minioPreType) + timeUuidFilename;
// 设置及Minio基础信息
MinioFilePath minioFIlePath = new MinioFilePath();
minioFIlePath.setFilename(filename);
minioFIlePath.setUuidFilename(uuidFilename);
minioFIlePath.setTimeUuidFilename(timeUuidFilename);
minioFIlePath.setFilepath(filepath);
minioFIlePath.setBucketNameFilepath(buckNameFilepath);
return minioFIlePath;
}
/**
* * 上传文件并返回处理信息
*/
public MinioFilePath uploadObjectReturnFilePath(MultipartFile file, String minioPreType) throws IOException {
if (file == null) return null;
String bucketName = properties.getBucketName();
// 上传对象
try {
MinioFilePath minioFile = initUploadFileReturnMinioFilePath(bucketName, minioPreType, file);
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(minioFile.getFilepath()).stream(file.getInputStream(), file.getSize(), -1).build());
return minioFile;
} catch (Exception exception) {
throw new BunnyException(ResultCodeEnum.UPDATE_ERROR);
}
}
/**
* 获取默认bucket文件并返回字节数组
*
* @param objectName 对象名称
* @return 文件流对象
*/
public byte[] getBucketObjectByte(String objectName) {
String bucketName = properties.getBucketName();
try {
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
return getObjectResponse.readAllBytes();
} catch (Exception exception) {
exception.printStackTrace();
}
throw new BunnyException(ResultCodeEnum.GET_BUCKET_EXCEPTION);
}
/**
* 获取Minio全路径名Object带有桶名称
*
* @param objectName 对象名称
* @return 全路径
*/
public String getObjectNameFullPath(String objectName) {
String url = properties.getEndpointUrl();
return url + objectName;
}
/**
* 上传文件
*
* @param bucketName 桶名称
* @param filename 文件名
* @param inputStream 输入流
* @param size 大小
*/
public void putObject(String bucketName, String filename, InputStream inputStream, Long size) {
try {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filename).stream(inputStream, size, -1).build());
} catch (Exception exception) {
log.error("上传文件失败:{}", (Object) exception.getStackTrace());
throw new BunnyException(ResultCodeEnum.UPDATE_ERROR);
}
}
/**
* * 删除目标文件
*
* @param list 对象文件列表
*/
public void removeObjects(List<String> list) {
try {
String bucketName = properties.getBucketName();
List<DeleteObject> objectList = list.stream().map(DeleteObject::new).toList();
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectList).build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
throw new BunnyException("Error in deleting object " + error.objectName() + "; " + error.message());
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* * 更新文件
*
* @param bucketName 桶名称
* @param filepath 文件路径
* @param file 文件
*/
public void updateFile(String bucketName, String filepath, MultipartFile file) {
try {
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
} catch (IOException e) {
throw new BunnyException(e.getMessage());
}
}
}

48
dao/pom.xml Normal file
View File

@ -0,0 +1,48 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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>cn.bunny</groupId>
<artifactId>financial-admin-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dao</artifactId>
<packaging>jar</packaging>
<name>model</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- 实体类注解 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.14</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,48 @@
package cn.bunny.dao.common.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Schema(name = "BaseEntity", title = "基础信息字段", description = "基础信息字段")
public class BaseEntity implements Serializable {
@Schema(name = "id", title = "唯一标识")
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@Schema(name = "createTime", title = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(name = "updateTime", title = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Schema(name = "createUser", title = "创建用户")
@TableField(fill = FieldFill.INSERT)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JsonSerialize(using = ToStringSerializer.class)
private Long createUser;
@Schema(name = "updateUser", title = "操作用户")
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JsonSerialize(using = ToStringSerializer.class)
private Long updateUser;
@Schema(name = "isDeleted", title = "是否被删除")
@TableLogic
private Boolean isDeleted;
}

View File

@ -0,0 +1,18 @@
package cn.bunny.dao.common.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(name = "BaseUserEntity", title = "基础信息字段包含用户信息", description = "基础信息字段包含用户信息")
public class BaseUserEntity extends BaseEntity {
@Schema(name = "username", title = "用户名")
private String createUsername;
@Schema(name = "nickname", title = "昵称")
private String updateUsername;
}

View File

@ -0,0 +1,18 @@
package cn.bunny.dao.common.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(name = "BaseVo", title = "基础返回对象内容包含用户信息", description = "基础返回对象内容包含用户信息")
public class BaseUserVo extends BaseVo {
@Schema(name = "username", title = "用户名")
private String createUsername;
@Schema(name = "nickname", title = "昵称")
private String updateUsername;
}

View File

@ -0,0 +1,49 @@
package cn.bunny.dao.common.vo;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Schema(name = "BaseVo", title = "基础返回对象内容", description = "基础返回对象内容")
public class BaseVo implements Serializable {
@Schema(name = "id", title = "主键")
@JsonProperty("id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@Schema(name = "updateTime", title = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;
@Schema(name = "createTime", title = "发布时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;
@Schema(name = "createUser", title = "创建用户")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long createUser;
@Schema(name = "updateUser", title = "操作用户")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long updateUser;
}

View File

@ -0,0 +1,42 @@
package cn.bunny.dao.common.vo;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(name = "TreeSelectList对象", title = "树形结构数据", description = "树形结构数据")
public class TreeSelectVo {
@Schema(name = "id", title = "主键")
@JsonProperty("id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@Schema(name = "value", title = "值内容")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Object value;
@Schema(name = "key", title = "key内容")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Object key;
@Schema(name = "label", title = "显示内容")
private String label;
@Schema(name = "parentId", title = "父级id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long parentId;
@Schema(name = "children", title = "子级内容")
private List<TreeSelectVo> children;
}

View File

@ -0,0 +1,46 @@
package cn.bunny.dao.dto.financial.bill;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "BillAddDto对象", title = "账单信息添加内容", description = "账单信息添加内容")
public class BillAddDto {
@Schema(name = "username", title = "类型1 - 收入,-1 - 支出")
@NotNull(message = "类型不能为空")
@Min(value = -1, message = "类型格式不正确")
@Max(value = 1, message = "类型格式不正确")
private Byte type;
@Schema(name = "amount", title = "金额")
@NotNull(message = "金额不能为空")
@Min(value = 0, message = "金额格式不正确")
private BigDecimal amount;
@Schema(name = "description", title = "描述")
private String description;
@Schema(name = "transactionDate", title = "交易日期")
@NotNull(message = "交易日期不能为空")
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime transactionDate;
@Schema(name = "categoryId", title = "类别id")
@NotNull(message = "类别id不能为空")
private Long categoryId;
}

View File

@ -0,0 +1,33 @@
package cn.bunny.dao.dto.financial.bill;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "BillDto对象", title = "账单信息查询内容", description = "账单信息查询内容")
public class BillDto {
@Schema(name = "username", title = "类型1 - 收入,-1 - 支出")
private Byte type;
@Schema(name = "description", title = "描述")
private String description;
@Schema(name = "startDate", title = "开始交易日期")
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd")
private LocalDate startDate;
@Schema(name = "endDate", title = "结束交易日期")
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd")
private LocalDate endDate;
}

View File

@ -0,0 +1,41 @@
package cn.bunny.dao.dto.financial.bill;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "BillUpdateDto对象", title = "账单信息更新内容", description = "账单信息更新内容")
public class BillUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "username", title = "类型1 - 收入,-1 - 支出")
private Byte type;
@Schema(name = "amount", title = "金额")
private BigDecimal amount;
@Schema(name = "description", title = "描述")
private String description;
@Schema(name = "transactionDate", title = "交易日期")
@JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime transactionDate;
@Schema(name = "categoryId", title = "类别id")
private Long categoryId;
}

View File

@ -0,0 +1,24 @@
package cn.bunny.dao.dto.financial.category;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryUserAddDto对象", title = "分类信息添加", description = "分类信息添加")
public class CategoryUserAddDto {
@Schema(name = "categoryName", title = "分类名称")
@NotNull(message = "分类名称不能为空")
@NotBlank(message = "分类名称不能为空")
private String categoryName;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.financial.category;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryDto对象", title = "分类信息查询", description = "分类信息查询")
public class CategoryUserDto {
@Schema(name = "categoryName", title = "分类名称")
private String categoryName;
@Schema(name = "isBuiltin", title = "是否内置字段")
private Boolean isBuiltin;
}

View File

@ -0,0 +1,27 @@
package cn.bunny.dao.dto.financial.category;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryUpdateDto对象", title = "分类信息添加", description = "分类信息添加")
public class CategoryUserUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "categoryName", title = "分类名称")
@NotNull(message = "分类名称不能为空")
@NotBlank(message = "分类名称不能为空")
private String categoryName;
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nAddDto对象", title = "多语言添加", description = "多语言添加")
public class I18nAddDto {
@Schema(name = "keyName", title = "多语言key")
@NotBlank(message = "多语言key不能为空")
@NotNull(message = "多语言key不能为空")
private String keyName;
@Schema(name = "translation", title = "多语言翻译名称")
@NotBlank(message = "多语言翻译名称不能为空")
@NotNull(message = "多语言翻译名称不能为空")
private String translation;
@Schema(name = "typeName", title = "多语言类型名称")
@NotBlank(message = "多语言类型名称不能为空")
@NotNull(message = "多语言类型名称不能为空")
private String typeName;
}

View File

@ -0,0 +1,26 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nDto对象", title = "多语言分页查询", description = "多语言分页查询")
public class I18nDto {
@Schema(name = "keyName", title = "多语言key")
private String keyName;
@Schema(name = "translation", title = "多语言翻译名称")
private String translation;
@Schema(name = "typeName", title = "多语言类型名称")
private String typeName;
}

View File

@ -0,0 +1,32 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nTypeAddDto对象", title = "多语言类型添加", description = "多语言类型添加内容")
public class I18nTypeAddDto {
@Schema(name = "typeName", title = "多语言类型(比如zh,en)")
@NotBlank(message = "多语言类型不能为空")
@NotNull(message = "多语言类型不能为空")
private String typeName;
@Schema(name = "summary", title = "名称解释(比如中文,英文)")
@NotNull(message = "名称解释不能为空")
private String summary;
@Schema(name = "isDefault", title = "是否为默认")
private Boolean isDefault = false;
}

View File

@ -0,0 +1,22 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nTypeDto对象", title = "多语言类型", description = "多语言类型")
public class I18nTypeDto {
@Schema(name = "typeName", title = "多语言类型(比如zh,en)")
private String typeName;
@Schema(name = "summary", title = "名称解释(比如中文,英文)")
private String summary;
}

View File

@ -0,0 +1,35 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nTypeUpdateDto对象", title = "多语言类型更新", description = "多语言类型更新内容")
public class I18nTypeUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "typeName", title = "多语言类型(比如zh,en)")
@NotBlank(message = "多语言类型不能为空")
@NotNull(message = "多语言类型不能为空")
private String typeName;
@Schema(name = "summary", title = "名称解释(比如中文,英文)")
@NotBlank(message = "名称解释不能为空")
@NotNull(message = "名称解释不能为空")
private String summary;
@Schema(name = "isDefault", title = "是否为默认")
private Boolean isDefault = false;
}

View File

@ -0,0 +1,36 @@
package cn.bunny.dao.dto.i18n;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "I18nUpdateDto对象", title = "多语言更新", description = "多语言更新")
public class I18nUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "keyName", title = "多语言key")
@NotBlank(message = "多语言key不能为空")
@NotNull(message = "多语言key不能为空")
private String keyName;
@Schema(name = "translation", title = "多语言翻译名称")
@NotBlank(message = "多语言翻译名称不能为空")
@NotNull(message = "多语言翻译名称不能为空")
private String translation;
@Schema(name = "typeId", title = "多语言类型id")
private Long typeId;
}

View File

@ -0,0 +1,31 @@
package cn.bunny.dao.dto.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "QuartzExecuteLogDto对象", title = "调度任务执行日志分页查询", description = "调度任务执行日志分页查询")
public class ScheduleExecuteLogDto {
@Schema(name = "jobName", title = "任务名称")
private String jobName;
@Schema(name = "jobGroup", title = "任务分组")
private String jobGroup;
@Schema(name = "jobClassName", title = "执行任务类名")
private String jobClassName;
@Schema(name = "cronExpression", title = "执行任务core表达式")
private String cronExpression;
@Schema(name = "triggerName", title = "触发器名称")
private String triggerName;
}

View File

@ -0,0 +1,41 @@
package cn.bunny.dao.dto.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "UserLoginLogDto对象", title = "用户登录日志分页查询", description = "用户登录日志分页查询")
public class UserLoginLogDto {
@Schema(name = "userId", title = "用户Id")
private Long userId;
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "token", title = "登录token")
private String token;
@Schema(name = "ipAddress", title = "登录Ip")
private String ipAddress;
@Schema(name = "ipRegion", title = "登录Ip归属地")
private String ipRegion;
@Schema(name = "userAgent", title = "登录时代理")
private String userAgent;
@Schema(name = "type", title = "操作类型")
private String type;
@Schema(name = "xRequestedWith", title = "标识客户端是否是通过Ajax发送请求的")
private String xRequestedWith;
}

View File

@ -0,0 +1,40 @@
package cn.bunny.dao.dto.log;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "UserLoginLogDto对象", title = "用户登录日志分页查询", description = "用户登录日志分页查询")
public class UserLoginLogUpdateDto {
@Schema(name = "userId", title = "用户Id")
private Long userId;
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "token", title = "登录token")
private String token;
@Schema(name = "ipAddress", title = "登录Ip")
private String ipAddress;
@Schema(name = "ipRegion", title = "登录Ip归属地")
private String ipRegion;
@Schema(name = "userAgent", title = "登录时代理")
private String userAgent;
@Schema(name = "type", title = "操作类型")
private String type;
@Schema(name = "xRequestedWith", title = "标识客户端是否是通过Ajax发送请求的")
private String xRequestedWith;
}

View File

@ -0,0 +1,28 @@
package cn.bunny.dao.dto.quartz;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersOperationDto对象", title = "Schedulers公共操作表单", description = "Schedulers公共操作表单")
public class SchedulersOperationDto {
@Schema(name = "jobName", title = "任务名称")
@NotBlank(message = "任务名称不能为空")
@NotNull(message = "任务名称不能为空")
private String jobName;
@Schema(name = "jobGroup", title = "任务分组")
@NotBlank(message = "任务分组不能为空")
@NotNull(message = "任务分组不能为空")
private String jobGroup;
}

View File

@ -0,0 +1,26 @@
package cn.bunny.dao.dto.quartz.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersGroupAddDto对象", title = "添加任务调度分组", description = "添加任务调度分组")
public class SchedulersGroupAddDto {
@Schema(name = "groupName", title = "分组名称")
@NotBlank(message = "分组名称不能为空")
@NotNull(message = "分组名称不能为空")
private String groupName;
@Schema(name = "description", title = "分组详情")
private String description;
}

View File

@ -0,0 +1,24 @@
package cn.bunny.dao.dto.quartz.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersGroupDto对象", title = "分页查询任务调度分组", description = "分页查询任务调度分组")
public class SchedulersGroupDto {
@Schema(name = "groupName", title = "分组名称")
private String groupName;
@Schema(name = "description", title = "分组详情")
private String description;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.quartz.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersGroupUpdateDto对象", title = "更新任务调度分组", description = "更新任务调度分组")
public class SchedulersGroupUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "groupName", title = "分组名称")
@NotBlank(message = "分组名称不能为空")
@NotNull(message = "分组名称不能为空")
private String groupName;
@Schema(name = "description", title = "分组详情")
private String description;
}

View File

@ -0,0 +1,44 @@
package cn.bunny.dao.dto.quartz.schedule;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersAddDto对象", title = "Schedulers添加表单", description = "Schedulers添加表单")
public class SchedulersAddDto {
@Schema(name = "jobName", title = "任务名称")
@NotBlank(message = "任务名称不能为空")
@NotNull(message = "任务名称不能为空")
private String jobName;
@Schema(name = "jobGroup", title = "任务分组")
@NotBlank(message = "任务分组不能为空")
@NotNull(message = "任务分组不能为空")
private String jobGroup;
@Schema(name = "description", title = "任务详情")
private String description;
@Schema(name = "jobClassName", title = "任务类名称")
@NotBlank(message = "corn表达式不能为空")
@NotNull(message = "corn表达式不能为空")
private String jobClassName;
@Schema(name = "cronExpression", title = "corn表达式")
@NotBlank(message = "corn表达式不能为空")
@NotNull(message = "corn表达式不能为空")
private String cronExpression;
}

View File

@ -0,0 +1,40 @@
package cn.bunny.dao.dto.quartz.schedule;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersDto对象", title = "Schedulers查询表单", description = "Schedulers查询表单")
public class SchedulersDto {
@Schema(name = "jobName", title = "任务名称")
private String jobName;
@Schema(name = "jobGroup", title = "任务分组")
private String jobGroup;
@Schema(name = "description", title = "任务详情")
private String description;
@Schema(name = "jobClassName", title = "任务类名称")
@NotNull(message = "corn表达式不能为空")
private String jobClassName;
@Schema(name = "triggerName", title = "触发器名称")
private String triggerName;
@Schema(name = "triggerState", title = "triggerState触发器状态")
private String triggerState;
@Schema(name = "jobMethodName", title = "执行方法")
private String jobMethodName;
}

View File

@ -0,0 +1,48 @@
package cn.bunny.dao.dto.quartz.schedule;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "SchedulersUpdateDto对象", title = "Schedulers更新表单", description = "Schedulers更新表单")
public class SchedulersUpdateDto {
@Schema(name = "jobName", title = "任务名称")
@NotBlank(message = "任务名称不能为空")
@NotNull(message = "任务名称不能为空")
private String jobName;
@Schema(name = "jobGroup", title = "任务分组")
@NotBlank(message = "任务分组不能为空")
@NotNull(message = "任务分组不能为空")
private String jobGroup;
@Schema(name = "description", title = "任务详情")
@NotBlank(message = "任务详情不能为空")
@NotNull(message = "任务详情不能为空")
private String description;
@Schema(name = "jobClassName", title = "任务类名称")
@NotBlank(message = "corn表达式不能为空")
@NotNull(message = "corn表达式不能为空")
private String jobClassName;
@Schema(name = "cronExpression", title = "corn表达式")
@NotBlank(message = "corn表达式不能为空")
@NotNull(message = "corn表达式不能为空")
private String cronExpression;
@Schema(name = "jobMethodName", title = "执行方法")
@NotBlank(message = "执行方法不能为空")
@NotNull(message = "执行方法不能为空")
private String jobMethodName;
}

View File

@ -0,0 +1,132 @@
package cn.bunny.dao.dto.system.configuration;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@Getter
@Setter
@Accessors(chain = true)
@Schema(name = "WebConfiguration对象", title = "前端配置选项", description = "前端配置选项")
public class WebConfigurationDto {
@Schema(name = "Version", description = "应用程序的版本")
@NotBlank(message = "应用程序的版本不能为空")
@NotNull(message = "应用程序的版本不能为空")
private String version;
@Schema(name = "Title", description = "应用程序的标题")
@NotBlank(message = "应用程序的标题不能为空")
@NotNull(message = "应用程序的标题不能为空")
private String title;
@Schema(name = "Copyright", description = "版权信息")
@NotBlank(message = "版权信息不能为空")
@NotNull(message = "版权信息不能为空")
private String copyright;
@Schema(name = "FixedHeader", description = "头部是否固定")
@NotNull(message = "头部是否固定不能为空")
private boolean fixedHeader;
@Schema(name = "HiddenSideBar", description = "侧边栏是否隐藏")
@NotNull(message = "侧边栏是否隐藏不能为空")
private boolean hiddenSideBar;
@Schema(name = "MultiTagsCache", description = "是否缓存多个标签")
@NotNull(message = "是否缓存多个标签不能为空")
private boolean multiTagsCache;
@Schema(name = "KeepAlive", description = "是否持久化")
@NotNull(message = "持久化不能为空")
private boolean keepAlive;
@Schema(name = "Locale", description = "语言类型")
@NotBlank(message = "语言类型不能为空")
@NotNull(message = "语言类型不能为空")
private String locale;
@Schema(name = "Layout", description = "应用程序的布局")
@NotBlank(message = "应用程序的布局不能为空")
@NotNull(message = "应用程序的布局不能为空")
private String layout;
@Schema(name = "Theme", description = "应用程序的主题")
@NotBlank(message = "管理者不能为空")
@NotNull(message = "管理者不能为空")
private String theme;
@Schema(name = "DarkMode", description = "是否启用深色模式")
@NotNull(message = "darkMode不能为空")
private boolean darkMode;
@Schema(name = "OverallStyle", description = "应用程序的整体样式")
@NotBlank(message = "整体样式不能为空")
@NotNull(message = "整体样式不能为空")
private String overallStyle;
@Schema(name = "Grey", description = "是否启用灰色模式")
@NotNull(message = "是否启用灰色模式不能为空")
private boolean grey;
@Schema(name = "Weak", description = "色弱模式")
@NotNull(message = "色弱模式不能为空")
private boolean weak;
@Schema(name = "HideTabs", description = "是否隐藏选项卡")
@NotNull(message = "是否隐藏选项卡不能为空")
private boolean hideTabs;
@Schema(name = "HideFooter", description = "是否隐藏页脚")
@NotNull(message = "是否隐藏页脚不能为空")
private boolean hideFooter;
@Schema(name = "Stretch", description = "是否拉伸显示")
@NotNull(message = "是否拉伸不能为空")
private boolean stretch;
@Schema(name = "SidebarStatus", description = "侧边栏的状态")
@NotNull(message = "侧边栏的状态不能为空")
private boolean sidebarStatus;
@Schema(name = "EpThemeColor", description = "主题颜色")
@NotNull(message = "主题颜色不能为空")
private String epThemeColor;
@Schema(name = "ShowLogo", description = "是否显示logo")
@NotNull(message = "是否显示logo不能为空")
private boolean showLogo;
@Schema(name = "ShowModel", description = "要显示的模型")
@NotBlank(message = "showModel不能为空")
@NotNull(message = "showModel不能为空")
private String showModel;
@Schema(name = "MenuArrowIconNoTransition", description = "菜单箭头图标是否没有过渡效果")
@NotNull(message = "过渡效果不能为空")
private boolean menuArrowIconNoTransition;
@Schema(name = "CachingAsyncRoutes", description = "是否缓存异步路由")
@NotNull(message = "缓存异步路由不能为空")
private boolean cachingAsyncRoutes;
@Schema(name = "TooltipEffect", description = "工具提示的效果")
@NotBlank(message = "工具提示不能为空")
@NotNull(message = "工具提示不能为空")
private String tooltipEffect;
@Schema(name = "ResponsiveStorageNameSpace", description = "响应式存储的命名空间")
@NotBlank(message = "响应式存储的命名空间不能为空")
@NotNull(message = "响应式存储的命名空间不能为空")
private String responsiveStorageNameSpace;
@Schema(name = "MenuSearchHistory", description = "菜单搜索历史")
@NotNull(message = "菜单搜索历史不能为空")
@Min(value = 1, message = "菜单搜索历史必须大于等于1")
private int menuSearchHistory;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.system.configuration.category;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryAddDto对象", title = "分类信息添加", description = "分类信息添加")
public class CategoryAddDto {
@Schema(name = "userId", title = "绑定的用户id")
private Long userId;
@Schema(name = "categoryName", title = "分类名称")
@NotNull(message = "分类名称不能为空")
@NotBlank(message = "分类名称不能为空")
private String categoryName;
@Schema(name = "isBuiltin", title = "是否内置字段")
@NotNull(message = "内置字段不能为空")
private Boolean isBuiltin;
}

View File

@ -0,0 +1,25 @@
package cn.bunny.dao.dto.system.configuration.category;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryDto对象", title = "分类信息查询", description = "分类信息查询")
public class CategoryDto {
@Schema(name = "userId", title = "绑定的用户id")
private Long userId;
@Schema(name = "categoryName", title = "分类名称")
private String categoryName;
@Schema(name = "isBuiltin", title = "是否内置字段")
private Boolean isBuiltin;
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.dto.system.configuration.category;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "CategoryUpdateDto对象", title = "分类信息添加", description = "分类信息添加")
public class CategoryUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "userId", title = "绑定的用户id")
private Long userId;
@Schema(name = "categoryName", title = "分类名称")
@NotNull(message = "分类名称不能为空")
@NotBlank(message = "分类名称不能为空")
private String categoryName;
@Schema(name = "isBuiltin", title = "是否内置字段")
@NotNull(message = "内置字段不能为空")
private Boolean isBuiltin;
}

View File

@ -0,0 +1,38 @@
package cn.bunny.dao.dto.system.dept;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "DeptAddDto对象", title = "部门", description = "部门管理")
public class DeptAddDto {
@Schema(name = "parentId", title = "父级id")
private String parentId;
@Schema(name = "managerId", title = "管理者")
@NotNull(message = "管理者不能为空")
@NotEmpty(message = "管理者不能为空")
private List<String> manager;
@Schema(name = "deptName", title = "部门名称")
@NotBlank(message = "部门名称不能为空")
@NotNull(message = "部门名称不能为空")
private String deptName;
@Schema(name = "summary", title = "部门简介")
private String summary;
}

View File

@ -0,0 +1,23 @@
package cn.bunny.dao.dto.system.dept;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "DeptDto对象", title = "部门", description = "部门管理")
public class DeptDto {
@Schema(name = "deptName", title = "部门名称")
private String deptName;
@Schema(name = "summary", title = "部门简介")
private String summary;
}

View File

@ -0,0 +1,42 @@
package cn.bunny.dao.dto.system.dept;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "DeptUpdateDto对象", title = "部门", description = "部门管理")
public class DeptUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "parentId", title = "父级id")
private Long parentId;
@Schema(name = "managerId", title = "管理者")
@NotNull(message = "管理者不能为空")
@NotEmpty(message = "管理者不能为空")
private List<String> manager;
@Schema(name = "deptName", title = "部门名称")
@NotBlank(message = "部门名称不能为空")
@NotNull(message = "部门名称不能为空")
private String deptName;
@Schema(name = "summary", title = "部门简介")
private String summary;
}

View File

@ -0,0 +1,39 @@
package cn.bunny.dao.dto.system.email.template;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailTemplateAddDto对象", title = "邮箱模板请求内容", description = "邮箱模板请求内容")
public class EmailTemplateAddDto {
@Schema(name = "templateName", title = "模板名称")
@NotBlank(message = "模板名称不能为空")
@NotNull(message = "模板名称不能为空")
private String templateName;
@Schema(name = "emailUser", title = "配置邮件用户")
@NotNull(message = "配置邮件用户不能为空")
private Long emailUser;
@Schema(name = "subject", title = "主题")
@NotBlank(message = "主题不能为空")
@NotNull(message = "主题不能为空")
private String subject;
@Schema(name = "body", title = "邮件内容")
@NotBlank(message = "邮件内容不能为空")
@NotNull(message = "邮件内容不能为空")
private String body;
@Schema(name = "type", title = "邮件类型")
private String type;
}

View File

@ -0,0 +1,28 @@
package cn.bunny.dao.dto.system.email.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailTemplateDto", title = "邮箱模板请求内容", description = "邮箱模板请求内容")
public class EmailTemplateDto {
@Schema(name = "templateName", title = "模板名称")
private String templateName;
@Schema(name = "subject", title = "主题")
private String subject;
@Schema(name = "body", title = "邮件内容")
private String body;
@Schema(name = "type", title = "邮件类型")
private String type;
}

View File

@ -0,0 +1,43 @@
package cn.bunny.dao.dto.system.email.template;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailTemplateUpdateDto对象", title = "邮箱模板请求内容", description = "邮箱模板请求内容")
public class EmailTemplateUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "templateName", title = "模板名称")
@NotBlank(message = "模板名称不能为空")
@NotNull(message = "模板名称不能为空")
private String templateName;
@Schema(name = "emailUser", title = "配置邮件用户")
@NotNull(message = "配置邮件用户不能为空")
private Long emailUser;
@Schema(name = "subject", title = "主题")
@NotBlank(message = "主题不能为空")
@NotNull(message = "主题不能为空")
private String subject;
@Schema(name = "body", title = "邮件内容")
@NotBlank(message = "邮件内容不能为空")
@NotNull(message = "邮件内容不能为空")
private String body;
@Schema(name = "type", title = "邮件类型")
private String type;
}

View File

@ -0,0 +1,24 @@
package cn.bunny.dao.dto.system.email.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailUserUpdateStatusDto对象", title = "邮箱用户更新状态", description = "邮箱用户更新状态表单")
public class EmailUserUpdateStatusDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "isDefault", title = "是否为默认邮件")
private Boolean isDefault;
}

View File

@ -0,0 +1,43 @@
package cn.bunny.dao.dto.system.email.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailUsersAddDto对象", title = "邮箱用户发送配置", description = "邮箱用户发送配置管理")
public class EmailUsersAddDto {
@Schema(name = "email", title = "邮箱")
@NotBlank(message = "邮箱不能为空")
@NotNull(message = "邮箱不能为空")
private String email;
@Schema(name = "password", title = "密码")
@NotBlank(message = "密码不能为空")
@NotNull(message = "密码不能为空")
private String password;
@Schema(name = "host", title = "Host地址")
@NotBlank(message = "Host地址不能为空")
@NotNull(message = "Host地址不能为空")
private String host;
@Schema(name = "port", title = "端口号")
@NotNull(message = "端口号不能为空")
private Integer port;
@Schema(name = "smtpAgreement", title = "邮箱协议")
private String smtpAgreement;
@Schema(name = "isDefault", title = "是否为默认邮件")
private Boolean isDefault = false;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.system.email.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailUsersDto对象", title = "邮箱用户发送配置", description = "邮箱用户发送配置管理")
public class EmailUsersDto {
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "host", title = "Host地址")
private String host;
@Schema(name = "port", title = "端口号")
private Integer port;
@Schema(name = "smtpAgreement", title = "邮箱协议")
private String smtpAgreement;
}

View File

@ -0,0 +1,49 @@
package cn.bunny.dao.dto.system.email.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "EmailUsersUpdateDto对象", title = "邮箱用户发送配置", description = "邮箱用户发送配置管理")
public class EmailUsersUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "email", title = "邮箱")
@NotBlank(message = "邮箱不能为空")
@NotNull(message = "邮箱不能为空")
private String email;
@Schema(name = "password", title = "密码")
@NotBlank(message = "密码不能为空")
@NotNull(message = "密码不能为空")
private String password;
@Schema(name = "host", title = "Host地址")
@NotBlank(message = "Host地址不能为空")
@NotNull(message = "Host地址不能为空")
private String host;
@Schema(name = "port", title = "端口号")
@NotNull(message = "端口号不能为空")
private Integer port;
@Schema(name = "smtpAgreement", title = "邮箱协议")
private String smtpAgreement;
@Schema(name = "isDefault", title = "是否为默认邮件")
private Boolean isDefault;
}

View File

@ -0,0 +1,28 @@
package cn.bunny.dao.dto.system.files;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "FileUploadDto对象", title = "文件上传", description = "文件上传管理")
public class FileUploadDto {
@Schema(name = "file", title = "文件")
@NotNull(message = "文件不能为空")
private MultipartFile file;
@Schema(name = "type", title = "文件类型")
@NotBlank(message = "文件类型不能为空")
@NotNull(message = "文件类型不能为空")
private String type;
}

View File

@ -0,0 +1,37 @@
package cn.bunny.dao.dto.system.files;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "FilesAddDto对象", title = "文件", description = "文件管理")
public class FilesAddDto {
@Schema(name = "filepath", title = "文件在服务器上的存储路径")
@NotBlank(message = "存储路径不能为空")
@NotNull(message = "存储路径不能为空")
private String filepath;
@Schema(name = "downloadCount", title = "下载数量")
@Min(value = 0L, message = "最小值为0")
private Integer downloadCount = 0;
@Schema(name = "files", title = "文件")
@NotEmpty(message = "文件不能为空")
@NotNull(message = "文件不能为空")
private List<MultipartFile> files;
}

View File

@ -0,0 +1,28 @@
package cn.bunny.dao.dto.system.files;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "FilesDto对象", title = "文件", description = "文件管理")
public class FilesDto {
@Schema(name = "filename", title = "文件的名称")
private String filename;
@Schema(name = "filepath", title = "文件在服务器上的存储路径")
private String filepath;
@Schema(name = "fileType", title = "文件的MIME类型")
private String fileType;
@Schema(name = "downloadCount", title = "下载数量")
private Integer downloadCount;
}

View File

@ -0,0 +1,41 @@
package cn.bunny.dao.dto.system.files;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "FilesUpdateDto对象", title = "文件", description = "文件管理")
public class FilesUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "filename", title = "文件的名称")
@NotBlank(message = "文件的名称不能为空")
@NotNull(message = "文件的名称不能为空")
private String filename;
@Schema(name = "fileType", title = "文件的MIME类型")
@NotBlank(message = "文件类型不能为空")
@NotNull(message = "文件类型不能为空")
private String fileType;
@Schema(name = "downloadCount", title = "下载数量")
@Min(value = 0L, message = "最小值为0")
private Integer downloadCount;
@Schema(name = "file", title = "文件")
private MultipartFile files;
}

View File

@ -0,0 +1,29 @@
package cn.bunny.dao.dto.system.menuIcon;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MenuIconAddDto对象", title = "系统菜单图标", description = "系统菜单图标管理")
public class MenuIconAddDto {
@Schema(name = "iconCode", title = "icon类名")
@NotBlank(message = "iconCode不能为空")
@NotNull(message = "iconCode不能为空")
private String iconCode;
@Schema(name = "iconName", title = "icon 名称")
@NotBlank(message = "icon 名称不能为空")
@NotNull(message = "icon 名称不能为空")
private String iconName;
}

View File

@ -0,0 +1,23 @@
package cn.bunny.dao.dto.system.menuIcon;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MenuIconDto对象", title = "系统菜单图标", description = "系统菜单图标管理")
public class MenuIconDto {
@Schema(name = "iconCode", title = "icon类名")
private String iconCode;
@Schema(name = "iconName", title = "icon 名称")
private String iconName;
}

View File

@ -0,0 +1,32 @@
package cn.bunny.dao.dto.system.menuIcon;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MenuIconUpdateDto对象", title = "系统菜单图标", description = "系统菜单图标管理")
public class MenuIconUpdateDto {
@Schema(name = "id", title = "主键")
@NotNull(message = "id不能为空")
private Long id;
@Schema(name = "iconCode", title = "icon类名")
@NotBlank(message = "iconCode不能为空")
@NotNull(message = "iconCode不能为空")
private String iconCode;
@Schema(name = "iconName", title = "icon 名称")
@NotBlank(message = "icon 名称不能为空")
@NotNull(message = "icon 名称不能为空")
private String iconName;
}

View File

@ -0,0 +1,62 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageAddDto对象", title = "添加系统消息", description = "添加系统消息")
public class MessageAddDto {
@Schema(name = "title", title = "消息标题")
@NotNull(message = "消息标题 不能为空")
@NotBlank(message = "消息标题 不能为空")
private String title;
@Schema(name = "receivedUserIds", title = "接收人用户ID")
private List<Long> receivedUserIds;
@Schema(name = "sendUserId", title = "发送人用户ID")
private Long sendUserId;
@Schema(name = "messageTypeId", title = "消息类型")
@NotNull(message = "消息类型 不能为空")
private Long messageTypeId;
@Schema(name = "cover", title = "封面")
private String cover;
@Schema(name = "summary", title = "消息简介")
@NotBlank(message = "消息简介 不能为空")
@NotNull(message = "消息简介 不能为空")
private String summary;
@Schema(name = "content", title = "消息内容")
@NotBlank(message = "消息内容 不能为空")
@NotNull(message = "消息内容 不能为空")
private String content;
@Schema(name = "editorType", title = "编辑器类型")
@NotBlank(message = "编辑器类型 不能为空")
@NotNull(message = "编辑器类型 不能为空")
private String editorType;
@Schema(name = "status", title = "0:未读 1:已读")
private Boolean status = false;
@Schema(name = "level", title = "消息等级")
private String level;
@Schema(name = "extra", title = "消息等级详情")
private String extra;
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageDto对象", title = "消息查询", description = "消息查询")
public class MessageDto {
@Schema(name = "title", title = "消息标题")
private String title;
@Schema(name = "sendNickname", title = "发送人用户昵称")
private String sendNickname;
@Schema(name = "messageType", title = "消息类型")
private String messageType;
@Schema(name = "level", title = "消息等级")
private String level;
@Schema(name = "extra", title = "消息等级详情")
private String extra;
@Schema(name = "editorType", title = "编辑器类型")
private String editorType;
}

View File

@ -0,0 +1,39 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageReceivedDto对象", title = "用户消息查询", description = "用户消息查询")
public class MessageReceivedDto {
@Schema(name = "title", title = "消息标题")
private String title;
@Schema(name = "sendNickname", title = "发送人用户昵称")
private String sendNickname;
@Schema(name = "messageType", title = "消息类型")
private String messageType;
@Schema(name = "level", title = "消息等级")
private String level;
@Schema(name = "extra", title = "消息等级详情")
private String extra;
@Schema(name = "status", title = "0:未读 1:已读")
private Boolean status;
@Schema(name = "editorType", title = "编辑器类型")
private String editorType;
}

View File

@ -0,0 +1,24 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageReceivedDto对象", title = "用户消息查询", description = "用户消息查询")
public class MessageReceivedUpdateDto {
@Schema(name = "ids", title = "消息接受id")
private List<Long> ids;
@Schema(name = "status", title = "0:未读 1:已读")
private Boolean status;
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageTypeAddDto对象", title = "系统消息类型", description = "系统消息类型")
public class MessageTypeAddDto {
@Schema(name = "messageName", title = "消息名称")
@NotBlank(message = "消息名称 不能为空")
@NotNull(message = "消息名称 不能为空")
private String messageName;
@Schema(name = "messageType", title = "sys:系统消息,user用户消息")
@NotBlank(message = "消息类型 不能为空")
@NotNull(message = "消息类型 不能为空")
private String messageType;
@Schema(name = "summary", title = "消息备注")
private String summary;
@Schema(name = "status", title = "消息类型")
private Boolean status = true;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.system.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(name = "MessageType对象", title = "系统消息类型", description = "系统消息类型")
public class MessageTypeDto {
@Schema(name = "status", title = "1:启用 0:禁用")
private Boolean status;
@Schema(name = "messageName", title = "消息名称")
private String messageName;
@Schema(name = "messageType", title = "sys:系统消息,user用户消息")
private String messageType;
@Schema(name = "summary", title = "消息备注")
private String summary;
}

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