This commit is contained in:
bunny 2024-09-13 15:14:58 +08:00
commit 0c7c85248e
131 changed files with 5966 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
services/HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### 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/

384
ReadMe.md Normal file
View File

@ -0,0 +1,384 @@
# Spring模板
每个服务下都有`Dockerfile`文件,几乎是写好的模板,如果要添加在这基础上即可。
- 基础包有
- 邮件发送
- WebSocket
- Minio
- Redis
- rabbitMq
- velocity
- IP地址查询
- knife4j
- 数据库多源配置
## 基础配置
### 配置文件详情
### 打包命令
命令解释:清理之前内容,打包,使用生产环境,跳过测试
```shell
mvn clean package -Pprod -DskipTests
```
#### SpringBoot配置文件
在开发中需要使用到开发环境、上线需要生产环境,在环境中设置`@profiles.active@`可以根据不同环境切换
```yaml
spring:
profiles:
active: @profiles.active@
application:
name: service-admin
```
只需要在IDE中勾选相关环境即可
![image-20240822093552802](./images/image-20240822093552802.png)
> 注意!!!
>
> 因为Java每次启动都需要生成target有缓存在里面很有可能明明选择了配置但是没有生效的情况。
>
> 解决办法就是,每次改变环境执行`mvn clean`或者点击IDE中`mvn clean`
>
> ![image-20240822093803078](./images/image-20240822093803078.png)
### Dockerfile配置
如果需要访问宿主机文件目录这个是Docker内部地址
```dockerfile
# 程序内部挂在目录
VOLUME /home/server/uploads
```
#### IDE中配置
![image-20240822094021339](./images/image-20240822094021339.png)
![image-20240822094000542](./images/image-20240822094000542.png)
### 整体返回响应
整体返回响应如下
```java
// 状态码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
```
![image-20240822092441020](./images/image-20240822092441020.png)
和分页返回
```java
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ResultPage<T> implements Serializable {
// 当前页
private Integer pageNo;
// 每页记录数
private Integer pageSize;
// 总记录数
private long total;
// 当前页数据集合
private List<T> list;
}
```
以及常用的枚举状态码(展示部分)
![image-20240822092510151](./images/image-20240822092510151.png)
### 多数据库源配置
开发中有时会使用到多个数据库源这个配置也是来自MybatisPlus官方推荐的库
```xml
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.1</version>
</dependency>
```
#### 配置简介
如果不需要多数据库,移除包之后将注释的部分放开,删除`dynamic`节点以下内容
```java
datasource:
# type: com.zaxxer.hikari.HikariDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://${bunny.datasource.host}:${bunny.datasource.port}/${bunny.datasource.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
# username: ${bunny.datasource.username}
# password: ${bunny.datasource.password}
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
grace-destroy: false #是否优雅关闭数据源默认为false设置为true时关闭数据源时如果数据源中还存在活跃连接至多等待10s后强制关闭
datasource:
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${bunny.datasource.host}:${bunny.datasource.port}/${bunny.datasource.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true
username: ${bunny.datasource.username}
password: ${bunny.datasource.password}
aop:
enabled: true
```
## 中间件配置
### mybatis-plus
#### 配置详情
配置乐观锁、防止全表删除、最大分页100页
`common/service-utils/src/main/java/cn/bunny/common/service/config/MybatisPlusConfig.java`
```java
/**
* Mybatis-Plus配置类
*/
@EnableTransactionManagement
@Configuration
@Slf4j
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(100L);// 设置最大分页为100
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
```
如果如要插入和修改时,自定义时间或者其它可以在这设置
`common/service-utils/src/main/java/cn/bunny/common/service/config/MyBatisPlusFieldConfig.java`
```java
/**
* 配置MP在修改和新增时的操作
*/
@Component
public class MyBatisPlusFieldConfig implements MetaObjectHandler {
/**
* 使用mp做添加操作时候这个方法执行
*/
@Override
public void insertFill(MetaObject metaObject) {
// 设置属性值
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("deleteStatus", 1, metaObject);
if (BaseContext.getUsername() != null) {
this.setFieldValByName("createBy", BaseContext.getUsername(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
}
}
/**
* 使用mp做修改操作时候这个方法执行
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", BaseContext.getUsername(), metaObject);
}
}
```
### Redis
#### 配置详情
分别设置了过期30天、1小时、3分钟
`common/service-utils/src/main/java/cn/bunny/common/service/config/RedisConfiguration.java`
```java
/**
* * 配置Redis过期时间30天
* 解决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过期时间3分钟
*
* @param factory
* @return
*/
@Bean
@SuppressWarnings("all")
public CacheManager cacheManagerWithMinutes(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3));
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();
}
```
#### 使用详情
如果需要指定Redis配置`cacheManager="Redis配置中方法名"`
使用springCache只需要在方法上加上下面代码
```java
@Cacheable(value = "TaskStatistics", key = "'ByDepartment::'+#departmentName",
cacheManager = "cacheManagerWithMinutes")
```
### Minio
#### 配置详情
Minio没有给出SpringBoot的配置文件下面是自定义实现
`module/module-minio/src/main/java/cn/bunny/module/minio/properties/MinioProperties.java`
在配置文件中有这4个配置字段
```java
@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() {
log.info("注册MinioClient...");
return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build();
}
}
```
在项目中加入了Minio常用工具方法对Minio二次封装
![image-20240822091720866](./images/image-20240822091720866.png)
### 邮箱发送
邮箱发送配置的是动态邮件,发件人是动态的不是写死在配置文件中
![image-20240822091810597](./images/image-20240822091810597.png)
#### 配置文件
如果不需要动态配置可以在`SpringBoot`配置文件中加入下面的配置
```properties
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 # 必要设置!!!
```
### SpringSecurity
因为项目做的是开发模板在admin模板中集成了安全框架
`module/spring-security/src/main/java/cn/bunny/security/config/WebSecurityConfig.java`
在这个文件最下面是排除路径不需要Security检测的路径根据自己需求进行修改因为整合了knife4j在测试时需要放开swagger配置响应请求等。
```java
/**
* * 排出鉴定路径
*
* @return WebSecurityCustomizer
*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
String[] annotations = {"/", "/test/**", "/diagram-viewer/**", "/editor-app/**", "/*.html",
"/*/*/noAuth/**", "/*/noAuth/**", "/favicon.ico", "/swagger-resources/**", "/webjars/**",
"/v3/**", "/swagger-ui.html/**", "/doc.html"};
return web -> web.ignoring().requestMatchers(annotations);
}
```

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common-generator</artifactId>
<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>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,89 @@
package cn.bunny.common.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.*
/**
* 代码生成器
*/
class NewCodeGet {
companion object {
// 数据连接
private const val SQL_HOST: String =
"jdbc:mysql://106.15.251.123:3305/bunny_docs?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true"
// 作者名称
private const val AUTHOR: String = "Bunny"
// 公共路径
private const val OUTPUT_DIR: String = "D:\\MyFolder\\Bunny\\Bunny-cli\\Java\\kotlin-single\\services"
// 实体类名称
private const val ENTITY: String = "Bunny"
@JvmStatic
fun main(args: Array<String>) {
generation("article")
}
/**
* 根据表名生成相应结构代码
*
* @param tableName 表名
*/
private fun generation(vararg tableName: String) {
// TODO 修改数据库路径、账户、密码
FastAutoGenerator.create(SQL_HOST, "root", "02120212")
.globalConfig { builder ->
// 添加作者名称
builder.author(AUTHOR) // 启用swagger
.enableSwagger() // 指定输出目录
.outputDir("$OUTPUT_DIR/src/main/java")
}
.packageConfig { builder ->
builder.entity(ENTITY) // 实体类包名
// TODO 父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名
.parent("cn.bunny.service.web")
.controller("controller") // 控制层包名
.mapper("mapper") // mapper层包名
.service("service") // service层包名
.serviceImpl("service.impl") // service实现类包名
// 自定义mapper.xml文件输出目录
.pathInfo(Collections.singletonMap(OutputFile.xml, "$OUTPUT_DIR/src/main/resources/mapper"))
}
.strategyConfig { builder ->
// 设置要生成的表名
builder.addInclude(*tableName) //.addTablePrefix("sys_")// TODO 设置表前缀过滤
.entityBuilder()
.enableLombok()
.enableChainModel()
.naming(NamingStrategy.underline_to_camel) // 数据表映射实体命名策略默认下划线转驼峰underline_to_camel
.columnNaming(NamingStrategy.underline_to_camel) // 表字段映射实体属性命名规则默认null不指定按照naming执行
.idType(IdType.AUTO) // TODO 添加全局主键类型
.formatFileName("%s") // 格式化实体名称,%s取消首字母I,
.mapperBuilder()
.mapperAnnotation(Mapper::class.java) // 开启mapper注解
.enableBaseResultMap() // 启用xml文件中的BaseResultMap 生成
.enableBaseColumnList() // 启用xml文件中的BaseColumnList
.formatMapperFileName("%sMapper") // 格式化Dao类名称
.formatXmlFileName("%sMapper") // 格式化xml文件名称
.serviceBuilder()
.formatServiceFileName("%sService") // 格式化 service 接口文件名称
.formatServiceImplFileName("%sServiceImpl") // 格式化 service 接口文件名称
.controllerBuilder()
.enableRestStyle()
}
// .injectionConfig(consumer -> {
// Map<String, String> customFile = new HashMap<>();
// // 配置DTO需要的话但是需要有能配置Dto的模板引擎比如freemarker但是这里我们用的VelocityEngine因此不多作介绍
// customFile.put(outputDir, "/src/main/resources/templates/entityDTO.java.ftl");
// consumer.customFile(customFile);
// })
.execute()
}
}
}

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common-service</artifactId>
<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>
<!-- 查询ip地址 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
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
open class Knife4jConfig {
@Bean
open fun openAPI(): OpenAPI {
// 作者等信息
val contact = Contact().name("Bunny").email("1319900154@qq.com").url("https://kotlinlang.org/docs")
// 使用协议
val license = License().name("MIT").url("https://MUT.com")
// 相关信息
val info = Info().title("Bunny-Java-Template").description("Bunny的Java模板").version("v1.0.0").contact(contact)
.license(license).termsOfService("维护不易~求个start")
return OpenAPI().info(info).externalDocs(ExternalDocumentation())
}
// 前台相关分类接口
@Bean
open fun groupedOpenApi(): GroupedOpenApi {
return GroupedOpenApi.builder().group("web前台接口管理").pathsToMatch("/api/**").build()
}
// 管理员相关分类接口
@Bean
open fun groupedOpenAdminApi(): GroupedOpenApi {
return GroupedOpenApi.builder().group("admin管理员接口请求").pathsToMatch("/admin/**").build()
}
}

View File

@ -0,0 +1,34 @@
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
class MyBatisPlusFieldConfig : MetaObjectHandler {
/**
* 使用mp做添加操作时候这个方法执行
*/
override fun insertFill(metaObject: MetaObject) {
this.strictInsertFill(metaObject, "isDeleted", Int::class.java, 0)
this.strictInsertFill(metaObject, "createTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictInsertFill(metaObject, "createUser", Long::class.java, BaseContext.getUserId())
this.strictInsertFill(metaObject, "updateUser", Long::class.java, BaseContext.getUserId())
}
/**
* 使用mp做修改操作时候这个方法执行
*/
override fun updateFill(metaObject: MetaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictUpdateFill(metaObject, "updateUser", Long::class.java, BaseContext.getUserId())
}
}

View File

@ -0,0 +1,34 @@
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
open class MybatisPlusConfig {
@Bean
open fun mybatisPlusInterceptor(): MybatisPlusInterceptor {
val interceptor = MybatisPlusInterceptor()
// 分页插件
val paginationInnerInterceptor = PaginationInnerInterceptor(DbType.MYSQL)
paginationInnerInterceptor.maxLimit = 100L // ? 设置最大分页为100
interceptor.addInnerInterceptor(paginationInnerInterceptor)
// 乐观锁
interceptor.addInnerInterceptor(OptimisticLockerInnerInterceptor())
// 防止全表删除
interceptor.addInnerInterceptor(BlockAttackInnerInterceptor())
return interceptor
}
}

View File

@ -0,0 +1,140 @@
package cn.bunny.common.service.config
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant
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
class RedisConfiguration {
/**
* 使用StringRedisSerializer序列化为字符串
*/
@Bean
fun redisTemplate(connectionFactory: LettuceConnectionFactory?): RedisTemplate<Any, Any> {
val redisTemplate = RedisTemplate<Any, Any>()
redisTemplate.connectionFactory = connectionFactory
// 设置key序列化为string
redisTemplate.keySerializer = StringRedisSerializer()
// 设置value序列化为JSON使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.valueSerializer = GenericJackson2JsonRedisSerializer()
redisTemplate.hashKeySerializer = StringRedisSerializer()
redisTemplate.hashValueSerializer = GenericJackson2JsonRedisSerializer()
return redisTemplate
}
/**
* * 配置Redis过期时间30天
* 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题
*/
@Bean
@Primary
fun cacheManagerWithMouth(factory: RedisConnectionFactory?): CacheManager {
// 配置序列化
val config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofDays(30))
return RedisCacheManager.builder(factory!!).cacheDefaults(config).build()
}
/**
* * 配置redis过期时间3分钟
*
* @param factory
* @return
*/
@Bean
fun cacheManagerWithMinutes(factory: RedisConnectionFactory?): CacheManager {
// 配置序列化
val config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3))
return RedisCacheManager.builder(factory!!).cacheDefaults(config).build()
}
/**
* * 配置Redis过期时间1小时
*
* @param factory
* @return
*/
@Bean
fun cacheManagerWithHours(factory: RedisConnectionFactory?): CacheManager {
// 配置序列化
val config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer()))
.entryTtl(Duration.ofHours(1))
return RedisCacheManager.builder(factory!!).cacheDefaults(config).build()
}
/**
* 指定的日期模式
*/
private fun jsonRedisSerializer(): Jackson2JsonRedisSerializer<Any> {
val mapper = ObjectMapper()
// 设置ObjectMapper访问权限
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
// 记录序列化之后的数据类型,方便反序列化
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL)
// LocalDatetime序列化默认不兼容jdk8日期序列化
val timeModule = JavaTimeModule()
timeModule.addDeserializer(
LocalDate::class.java,
LocalDateDeserializer(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD))
)
timeModule.addSerializer(
LocalDate::class.java,
LocalDateSerializer(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD))
)
timeModule.addDeserializer(
LocalDateTime::class.java,
LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS))
)
timeModule.addSerializer(
LocalDateTime::class.java,
LocalDateTimeSerializer(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS))
)
// 关闭默认的日期格式化方式默认UTC日期格式 yyyy-MM-ddTHH:mm:ss.SSS
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
mapper.registerModule(timeModule)
return Jackson2JsonRedisSerializer(mapper, Any::class.java)
}
}

View File

@ -0,0 +1,40 @@
package cn.bunny.common.service.config
import cn.bunny.common.service.interceptor.UserTokenInterceptor
import lombok.extern.slf4j.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration
@Slf4j
open class WebMvcConfiguration : WebMvcConfigurer {
@Autowired
private val userTokenInterceptor: UserTokenInterceptor? = null
/**
* 跨域配置
*
* @param registry 跨域注册表
*/
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**") // 是否发送Cookies
.allowCredentials(true) // 放行哪些原始域
.allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "DELETE").allowedHeaders("*")
.exposedHeaders("*")
}
override fun addInterceptors(registry: InterceptorRegistry) {
// TODO Spring Security 和这个拦截器任选一个
val excludeList = arrayOf(
"/", "/test/**", "/*.html", "/*/*/noAuth/**", "/*/noAuth/**", "/favicon.ico",
"/swagger-resources/**", "/swagger-ui.html/**", "/admin/login", "/v3/**", "/api/**"
)
registry.addInterceptor(userTokenInterceptor!!).excludePathPatterns(*excludeList)
// TODO 如果想使用普通JWT可以使用这个不使用 SpringSecurity6
// registry.addInterceptor(userTokenInterceptor).addPathPatterns("/api/**").excludePathPatterns(excludeList);
}
}

View File

@ -0,0 +1,50 @@
package cn.bunny.common.service.context
import cn.bunny.dao.vo.system.login.LoginVo
open class BaseContext {
companion object {
private val userId = ThreadLocal<Long>()
private val username = ThreadLocal<String>()
private val loginVo = ThreadLocal<LoginVo>()
// 用户id相关
@JvmStatic
fun getUserId(): Long {
return userId.get()
}
@JvmStatic
fun setUserId(id: Long) {
userId.set(id)
}
@JvmStatic
fun getUsername(): String {
return username.get()
}
@JvmStatic
fun setUsername(username: String) {
this.username.set(username)
}
@JvmStatic
fun getLoginVo(): LoginVo {
return loginVo.get()
}
@JvmStatic
fun setLoginVo(loginVo: LoginVo) {
this.loginVo.set(loginVo)
}
@JvmStatic
fun removeUser() {
username.remove()
userId.remove()
loginVo.remove()
}
}
}

View File

@ -0,0 +1,31 @@
package cn.bunny.common.service.exception
import cn.bunny.dao.pojo.result.ResultCodeEnum
import lombok.Getter
import lombok.ToString
import lombok.extern.slf4j.Slf4j
@Getter
@ToString
@Slf4j
class BunnyException : RuntimeException {
// 状态码
var code: Int? = null
// 描述信息
override var message: String
constructor(code: Int?, message: String) : super(message) {
this.code = code
this.message = message
}
constructor(message: String) : super(message) {
this.message = message
}
constructor(codeEnum: ResultCodeEnum) : super(codeEnum.message) {
this.code = codeEnum.code
this.message = codeEnum.message
}
}

View File

@ -0,0 +1,90 @@
package cn.bunny.common.service.exception
import cn.bunny.dao.pojo.constant.ExceptionConstant
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import lombok.extern.slf4j.Slf4j
import org.apache.logging.log4j.LogManager
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestControllerAdvice
import java.io.FileNotFoundException
import java.nio.file.AccessDeniedException
import java.sql.SQLIntegrityConstraintViolationException
@RestControllerAdvice
@Slf4j
class GlobalExceptionHandler {
private val logger = LogManager.getLogger(GlobalExceptionHandler::class.java)
// 自定义异常信息
@ExceptionHandler(BunnyException::class)
@ResponseBody
fun exceptionHandler(exception: BunnyException): Result<Any?> {
logger.error("GlobalExceptionHandler===>自定义异常信息:{}", exception.message)
val code = if (exception.code != null) exception.code else 500
return Result.error(null, code, exception.message)
}
// 运行时异常信息
@ExceptionHandler(RuntimeException::class)
@ResponseBody
@Throws(
FileNotFoundException::class
)
fun exceptionHandler(exception: RuntimeException): Result<Any?> {
logger.error("GlobalExceptionHandler===>运行时异常信息:{}", exception.message)
exception.printStackTrace()
return Result.error(null, 500, "出错了啦")
}
// 捕获系统异常
@ExceptionHandler(Exception::class)
@ResponseBody
fun error(exception: Exception): Result<Any?> {
logger.error("GlobalExceptionHandler===>系统异常信息:{}", exception.message)
return Result.error(null, 500, "系统异常")
}
// 特定异常处理
@ExceptionHandler(ArithmeticException::class)
@ResponseBody
fun error(exception: ArithmeticException): Result<Any?> {
logger.error("GlobalExceptionHandler===>特定异常信息:{}", exception.message)
return Result.error(null, 500, exception.message)
}
// spring security异常
@ExceptionHandler(AccessDeniedException::class)
@ResponseBody
@Throws(
AccessDeniedException::class
)
fun error(exception: AccessDeniedException): Result<String> {
logger.error("GlobalExceptionHandler===>spring security异常{}", exception.message)
return Result.error(ResultCodeEnum.SERVICE_ERROR)
}
// 处理SQL异常
@ExceptionHandler(SQLIntegrityConstraintViolationException::class)
@ResponseBody
fun exceptionHandler(exception: SQLIntegrityConstraintViolationException): Result<String> {
logger.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.message)
val message = exception.message
if (message!!.contains("Duplicate entry")) {
// 截取用户名
val username = message.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[2]
// 错误信息
val errorMessage = username + ExceptionConstant.ALREADY_USER_EXCEPTION
return Result.error(errorMessage)
} else {
return Result.error(ExceptionConstant.UNKNOWN_EXCEPTION)
}
}
}

View File

@ -0,0 +1,69 @@
package cn.bunny.common.service.interceptor
import cn.bunny.common.service.context.BaseContext
import cn.bunny.common.service.utils.JwtHelper
import cn.bunny.common.service.utils.ResponseUtil
import cn.bunny.dao.pojo.constant.RedisUserConstant
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import cn.bunny.dao.vo.system.login.LoginVo
import com.alibaba.fastjson2.JSONObject
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import lombok.extern.slf4j.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.lang.Nullable
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
/**
* * 微服务请求其它模块找不到Token无法从线程中获取值
* 传递请求头在微服务中
*/
@Component
@Slf4j
open class UserTokenInterceptor : HandlerInterceptor {
@Autowired
private val redisTemplate: RedisTemplate<Any, Any>? = null
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
// 不是动态方法直接返回
if (handler !is HandlerMethod) return true
val token = request.getHeader("token")
val mapByToken = JwtHelper.getMapByToken(token)
val loginVo = JSONObject.parseObject(JSONObject.toJSONString(mapByToken), LoginVo::class.java)
val redisUserinfo = redisTemplate!!.opsForValue()[RedisUserConstant.getUserLoginInfoPrefix(loginVo.email)]
// token过期-提示身份验证过期
if (JwtHelper.isExpired(token)) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.AUTHENTICATION_EXPIRED))
return false
}
// 解析不到userId
if (loginVo.id == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH))
return false
}
if (redisUserinfo == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH))
return false
}
BaseContext.setUserId(loginVo.id)
BaseContext.setUsername(loginVo.email)
BaseContext.setLoginVo(loginVo)
return true
}
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
@Nullable ex: Exception?
) {
BaseContext.removeUser()
}
}

View File

@ -0,0 +1,25 @@
package cn.bunny.common.service.utils
import cn.bunny.common.service.exception.BunnyException
import lombok.extern.slf4j.Slf4j
import org.apache.logging.log4j.LogManager
import org.springframework.util.StringUtils
@Slf4j
object EmptyUtil {
private val logger = LogManager.getLogger(EmptyUtil::class.java)
/**
* 是否为空
*
* @param value 判断值
* @param message 错误消息
*/
@JvmStatic
fun isEmpty(value: Any?, message: String?) {
if (value == null || !StringUtils.hasText(value.toString())) {
logger.error("为空对象错误:{}{}", value, message)
throw BunnyException(message!!)
}
}
}

View File

@ -0,0 +1,27 @@
package cn.bunny.common.service.utils
import org.springframework.stereotype.Component
/**
* * 计算文件大小
*/
@Component
class FileUtil {
companion object {
/**
* * 获取文件大小字符串
*/
fun getSize(fileSize: Long): String {
val fileSizeInKB = fileSize / 1024.00
val fileSizeInMB = fileSizeInKB / 1024
val fileSizeInGB = fileSizeInMB / 1024
return when {
fileSizeInGB >= 1 -> "${String.format("%.2f", fileSizeInGB).toDouble()}GB"
fileSizeInMB >= 1 -> "${String.format("%.2f", fileSizeInMB).toDouble()}MB"
fileSizeInKB >= 1 -> "${String.format("%.2f", fileSizeInKB).toDouble()}KB"
else -> "${fileSize}B"
}
}
}
}

View File

@ -0,0 +1,66 @@
package cn.bunny.common.service.utils
import cn.bunny.common.service.exception.BunnyException
import jakarta.annotation.PostConstruct
import lombok.extern.slf4j.Slf4j
import org.apache.logging.log4j.LogManager
import org.lionsoul.ip2region.xdb.Searcher
import org.springframework.core.io.ClassPathResource
import org.springframework.util.FileCopyUtils
import java.util.regex.Pattern
@Slf4j
class IpUtil {
companion object {
private val logger = LogManager.getLogger(IpUtil::class.java)
private var searcher: Searcher? = null
/**
* 判断是否为合法 IP
*/
private fun checkIp(ipAddress: String): Boolean {
val 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}"
val pattern = Pattern.compile(ip)
val matcher = pattern.matcher(ipAddress)
return matcher.matches()
}
/**
* 在服务启动时 ip2region 加载到内存中
*/
@PostConstruct
private fun initIp2Region() {
try {
val inputStream = ClassPathResource("/ipdb/ip2region.xdb").inputStream
val bytes = FileCopyUtils.copyToByteArray(inputStream)
searcher = Searcher.newWithBuffer(bytes)
} catch (exception: Exception) {
logger.error(exception.message)
}
}
/**
* 获取 ip 所属地址
*
* @param checkIp ip
*/
fun getIpRegion(checkIp: String): String {
val ip = if (checkIp == "0:0:0:0:0:0:0:1") "127.0.0.1" else checkIp
if (!checkIp(ip)) throw BunnyException("非法的IP地址")
val searchIpInfo = searcher?.search(ip)
return searchIpInfo?.let { it ->
val splitIpInfo = it.split("\\|".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (splitIpInfo.isNotEmpty()) {
when {
splitIpInfo[0] == "中国" -> splitIpInfo[2] // 国内属地返回省份
splitIpInfo[0] == "0" && splitIpInfo[4] == "内网IP" -> splitIpInfo[4] // 内网 IP
else -> splitIpInfo[0] // 国外属地返回国家
}
} else ""
} ?: ""
}
}
}

View File

@ -0,0 +1,368 @@
package cn.bunny.common.service.utils
import io.jsonwebtoken.CompressionCodecs
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.micrometer.common.lang.Nullable
import org.springframework.util.StringUtils
import java.util.*
object JwtHelper {
// 时间 按天 计算
private const val TOKEN_EXPIRATION = (24 * 60 * 60 * 1000).toLong()
// JWT 的 秘钥
private const val TOKEN_SIGN_KEY = "Bunny-Java-Template"
// 默认主题
private const val SUBJECT = "Bunny"
// 默认时间
private val time = Date(System.currentTimeMillis() + TOKEN_EXPIRATION * 7)
/**
* 使用默认主题默认时间默认秘钥创建自定义集合token
*
* @param map 集合
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?): String {
return Jwts.builder()
.setSubject(SUBJECT)
.setExpiration(time)
.signWith(SignatureAlgorithm.HS256, TOKEN_SIGN_KEY)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact()
}
/**
* 使用默认主题默认秘钥自定义时间创建集合形式token
*
* @param map 集合
* @param time 过期时间
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, time: Date?): String {
return Jwts.builder()
.setSubject(SUBJECT)
.signWith(SignatureAlgorithm.HS256, TOKEN_SIGN_KEY)
.setExpiration(time)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact()
}
/**
* 使用默认主题默认秘钥自定义时间创建集合形式token
*
* @param map 集合
* @param day 过期时间
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, day: Int): String {
return Jwts.builder()
.setSubject(SUBJECT)
.signWith(SignatureAlgorithm.HS256, TOKEN_SIGN_KEY)
.setExpiration(Date(System.currentTimeMillis() + TOKEN_EXPIRATION * day))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.compressWith(CompressionCodecs.GZIP).compact()
}
/**
* 使用默认主题默认秘钥自定义key创建集合形式token
*
* @param map 集合
* @param tokenSignKey 自定义key
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, tokenSignKey: String?): String {
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
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, subject: String?, time: Date?): String {
return Jwts.builder()
.setSubject(subject)
.setExpiration(time)
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, TOKEN_SIGN_KEY)
.compressWith(CompressionCodecs.GZIP)
.compact()
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param tokenSignKey 过期时间
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, subject: String?, tokenSignKey: String?): String {
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
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, tokenSignKey: String?, time: Int): String {
return Jwts.builder()
.setSubject(SUBJECT)
.setExpiration(Date(System.currentTimeMillis() + TOKEN_EXPIRATION * time))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact()
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param day 过期时间
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, subject: String?, tokenSignKey: String?, day: Int): String {
return Jwts.builder()
.setSubject(subject)
.setExpiration(Date(System.currentTimeMillis() + TOKEN_EXPIRATION * day))
.setClaims(map)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact()
}
/**
* 创建集合形式token
*
* @param map 集合
* @param subject 主题
* @param time 过期时间
* @return token
*/
@JvmStatic
fun createTokenWithMap(map: Map<String?, Any?>?, subject: String?, tokenSignKey: String?, time: Date?): String {
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值
*/
@JvmStatic
fun createToken(userId: Long?, userName: String?, day: Int): String {
return Jwts.builder()
.setSubject(SUBJECT)
.setExpiration(Date(System.currentTimeMillis() + TOKEN_EXPIRATION * day))
.claim("userId", userId)
.claim("userName", userName)
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.HS256, TOKEN_SIGN_KEY)
.compressWith(CompressionCodecs.GZIP)
.compact()
}
/**
* 使用token获取map集合,使用默认秘钥
*
* @param token token
* @return map集合
*/
@JvmStatic
fun getMapByToken(token: String?): Map<String, Any>? {
try {
if (!StringUtils.hasText(token)) return null
val claims = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token).body
// 将 body 值转为map
return HashMap(claims)
} catch (exception: Exception) {
return null
}
}
/**
* 使用token获取map集合
*
* @param token token
* @param signKey 秘钥
* @return map集合
*/
@JvmStatic
fun getMapByToken(token: String?, signKey: String?): Map<String, Any>? {
try {
if (!StringUtils.hasText(token)) return null
val claimsJws = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token)
val body = claimsJws.body
// 将 body 值转为map
return HashMap(body)
} catch (exception: Exception) {
return null
}
}
/**
* 根据token获取主题
*
* @param token token
* @return 主题
*/
@JvmStatic
fun getSubjectByToken(token: String): String? {
return getSubjectByTokenHandler(token, TOKEN_SIGN_KEY)
}
@Nullable
@JvmStatic
private fun getSubjectByTokenHandler(token: String, tokenSignKey: String): String? {
try {
if (!StringUtils.hasText(token)) return null
val claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token)
val body = claimsJws.body
return body.subject
} catch (exception: Exception) {
return null
}
}
/**
* 根据token获取主题
*
* @param token token
* @return 主题
*/
@JvmStatic
fun getSubjectByToken(token: String, tokenSignKey: String): String? {
return getSubjectByTokenHandler(token, tokenSignKey)
}
/**
* 根据token获取用户ID
*
* @param token token
* @return 用户ID
*/
@JvmStatic
fun getUserId(token: String?): Long? {
try {
if (!StringUtils.hasText(token)) return null
val claimsJws = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token)
val claims = claimsJws.body
return claims["userId"].toString().toLong()
} catch (exception: Exception) {
return null
}
}
/**
* 根据token获取用户名
*
* @param token token
* @return 用户名
*/
@JvmStatic
fun getUsername(token: String?): String? {
try {
if (!StringUtils.hasText(token)) return ""
val claimsJws = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token)
val claims = claimsJws.body
return claims["userName"] as String?
} catch (exception: Exception) {
return null
}
}
/**
* 判断token是否过期
*
* @param token token
* @return 是否过期
*/
@JvmStatic
fun isExpired(token: String): Boolean {
return isExpiredUtil(token, TOKEN_SIGN_KEY)
}
/**
* 判断token是否过期
*
* @param token token
* @return 是否过期
*/
@JvmStatic
fun isExpired(token: String, tokenSignKey: String): Boolean {
return isExpiredUtil(token, tokenSignKey)
}
/**
* 判断是否过期
*
* @param token token
* @param tokenSignKey key值
* @return 是否过期
*/
@JvmStatic
private fun isExpiredUtil(token: String, tokenSignKey: String): Boolean {
try {
val claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token)
val expiration = claimsJws.body.expiration
return expiration != null && expiration.before(Date())
} catch (exception: Exception) {
return true
}
}
}

View File

@ -0,0 +1,14 @@
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
class ResponseHandlerUtil {
companion object {
fun loginAuthHandler(response: HttpServletResponse, loginAuth: ResultCodeEnum): Boolean {
ResponseUtil.out(response, Result.error(loginAuth))
return false
}
}
}

View File

@ -0,0 +1,29 @@
package cn.bunny.common.service.utils
import cn.bunny.dao.pojo.email.EmailSend
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
open class ResponseUtil {
companion object {
@JvmStatic
fun out(response: HttpServletResponse, result: Result<Any?>?) {
try {
val mapper = ObjectMapper()
// 注册JavaTimeModule模块
mapper.registerModule(JavaTimeModule())
response.contentType = "application/json;charset=UTF-8"
response.status = HttpStatus.OK.value()
mapper.writeValue(response.writer, result)
val emailSend = EmailSend()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}

64
common/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<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>BunnyBBS-server-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>common</name>
<url>https://maven.apache.org</url>
<modules>
<module>common-generator</module>
<module>common-service</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencies>
<!-- dao 层 -->
<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>

135
dao/pom.xml Normal file
View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>BunnyBBS-server-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dao</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package cn.bunny.dao.dto.email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EmailTemplateDto {
// 模板名称
private String templateName;
// 主题
private String subject;
// 邮件内容
private String body;
// 邮件类型
private String type;
}

View File

@ -0,0 +1,30 @@
package cn.bunny.dao.dto.email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 添加邮箱用户
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EmailUsersDto {
// 修改时需要传
private Long id;
// 邮箱
private String email;
// 密码
private String password;
// SMTP服务器
private String host;
// 端口号
private Integer port;
// 邮箱协议
private Integer smtpAgreement;
// 是否为默认邮件
private Boolean isDefault;
}

View File

@ -0,0 +1,19 @@
package cn.bunny.dao.dto.user;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginDto {
// 用户名
private String username;
// 密码
private String password;
// 邮箱验证码
private String emailCode;
}

View File

@ -0,0 +1,45 @@
package cn.bunny.dao.entity.base;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseEntity implements Serializable {
@ApiModelProperty("唯一标识")
@TableId(value = "id", type = IdType.ASSIGN_ID)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@ApiModelProperty("创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@ApiModelProperty("创建用户ID")
@TableField(fill = FieldFill.INSERT)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long createUser;
@ApiModelProperty("操作用户ID")
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long updateUser;
@ApiModelProperty("是否被删除")
@TableLogic
private Boolean isDeleted;
}

View File

@ -0,0 +1,33 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_power")
@ApiModel(value = "AdminPower对象", description = "管理端权限")
public class AdminPower extends BaseEntity {
@ApiModelProperty("权限名称")
private String powerName;
@ApiModelProperty("权限编码")
private String powerCode;
@ApiModelProperty("描述")
private String description;
}

View File

@ -0,0 +1,36 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_role")
@ApiModel(value = "AdminRole对象", description = "管理端角色")
public class AdminRole extends BaseEntity implements Serializable {
@ApiModelProperty("角色名称")
private String roleName;
@ApiModelProperty("描述")
private String description;
@ApiModelProperty("角色代码")
private String roleCode;
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_role_power")
@ApiModel(value = "AdminRolePower对象", description = "AdminRolePower")
public class AdminRolePower extends BaseEntity {
@ApiModelProperty("角色id")
private String roleId;
@ApiModelProperty("权限id")
private String powerId;
@ApiModelProperty("描述")
private String description;
}

View File

@ -0,0 +1,57 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
* 管理员用户信息
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_user")
@ApiModel(value = "AdminUser对象", description = "管理员用户信息")
public class AdminUser extends BaseEntity implements Serializable {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("0:女 1:男")
private Byte sex;
@ApiModelProperty("个人描述")
private String personDescription;
@ApiModelProperty("最后登录IP")
private String lastLoginIp;
@ApiModelProperty("最后登录ip地址")
private String lastLoginIpAddress;
@ApiModelProperty("1:禁用 0:正常")
private Byte status;
}

View File

@ -0,0 +1,36 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-06-26
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("admin_user_role")
@ApiModel(value = "AdminUserRole对象", description = "管理端用户和角色关系")
public class AdminUserRole extends BaseEntity implements Serializable {
@ApiModelProperty("用户id")
private String userId;
@ApiModelProperty("角色id")
private String roleId;
@ApiModelProperty("描述")
private String description;
}

View File

@ -0,0 +1,22 @@
package cn.bunny.dao.entity.system.admin;
import cn.bunny.dao.entity.base.BaseEntity;
import lombok.*;
import java.io.Serializable;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AuthUserRole extends BaseEntity implements Serializable {
// 用户id
private Long userId;
// 角色id
private Long roleId;
// 角色代码
private String roleCode;
// 描述
private String roleDescription;
}

View File

@ -0,0 +1,42 @@
package cn.bunny.dao.entity.system.email;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author Bunny
* @since 2024-05-19
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("email_template")
@ApiModel(value = "EmailTemplate对象", description = "邮件模板")
public class EmailTemplate extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("模板名称")
private String templateName;
@ApiModelProperty("主题")
private String subject;
@ApiModelProperty("邮件内容")
private String body;
@ApiModelProperty("邮件类型")
private String type;
}

View File

@ -0,0 +1,49 @@
package cn.bunny.dao.entity.system.email;
import cn.bunny.dao.entity.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
/**
* <p>
* 邮箱发送表
* </p>
*
* @author Bunny
* @since 2024-05-17
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("email_users")
@ApiModel(value = "EmailUsers对象", description = "邮箱发送表")
public class EmailUsers extends BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("Host地址")
private String host;
@ApiModelProperty("端口号")
private Integer port;
@ApiModelProperty("邮箱协议")
private String smtpAgreement;
@ApiModelProperty("是否为默认邮件")
private Integer isDefault;
}

View File

@ -0,0 +1,74 @@
package cn.bunny.dao.entity.system.log;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 系统日志表
* </p>
*
* @author Bunny
* @since 2024-05-31
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("system_log")
@ApiModel(value = "SystemLog对象", description = "系统日志表")
public class SystemLog implements Serializable {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty("所在类路径")
private String classPath;
@ApiModelProperty("执行方法名称")
private String methodName;
@ApiModelProperty("入参内容")
private String args;
@ApiModelProperty("返回参数")
private String result;
@ApiModelProperty("报错堆栈")
private String errorStack;
@ApiModelProperty("报错")
private String errorMessage;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("用户名")
private String nickname;
@ApiModelProperty("当前用户token")
private String token;
@ApiModelProperty("当前用户IP地址")
private String ipAddress;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新用户")
private Long updateUser;
@ApiModelProperty("是否被删除")
private Boolean isDeleted;
}

View File

@ -0,0 +1,69 @@
package cn.bunny.dao.entity.system.user;
import cn.bunny.dao.entity.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
* 用户信息
* </p>
*
* @author Bunny
* @since 2024-05-17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@ApiModel(value = "User对象", description = "用户信息")
public class User extends BaseEntity implements Serializable {
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("手机号")
private String phone;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("0:女 1:男")
private Byte sex;
@ApiModelProperty("个人描述")
private String personDescription;
@ApiModelProperty("文章显示模式")
private String articleMode;
@ApiModelProperty("页面布局方式")
private String layout;
@ApiModelProperty("最后登录IP")
private String lastLoginIp;
@ApiModelProperty("最后登录ip地址")
private String lastLoginIpAddress;
@ApiModelProperty("积分")
private Integer totalIntegral;
@ApiModelProperty("当前积分")
private Integer currentIntegral;
@ApiModelProperty("0:禁用 1:正常")
private Boolean status;
}

View File

@ -0,0 +1,23 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
class ExceptionConstant {
companion object {
const val UNKNOWN_EXCEPTION: String = "未知错误"
// 用户相关
const val USER_NOT_LOGIN_EXCEPTION: String = "用户未登录"
const val USERNAME_IS_EMPTY_EXCEPTION: String = "用户名不能为空"
const val ALREADY_USER_EXCEPTION: String = "用户已存在"
const val USER_NOT_FOUND_EXCEPTION: String = "用户不存在"
// 密码相关
const val PASSWORD_EXCEPTION: String = "密码错误"
const val PASSWORD_NOT_EMPTY_EXCEPTION: String = "密码不能为空"
const val OLD_PASSWORD_EXCEPTION: String = "旧密码不匹配"
const val PASSWORD_EDIT_EXCEPTION: String = "密码修改失败"
const val OLD_PASSWORD_SAME_NEW_PASSWORD_EXCEPTION: String = "旧密码与新密码相同"
}
}

View File

@ -0,0 +1,12 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
class FileMessageConstant {
companion object {
const val STORAGE_OBJECT_EXCEPTION: String = "对象错误"
const val GET_BUCKET_EXCEPTION: String = "获取文件信息失败"
const val FILE_UPLOAD_EXCEPTION: String = "文件上传失败"
}
}

View File

@ -0,0 +1,11 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
object LocalDateTimeConstant {
const val YYYY_MM_DD: String = "yyyy-MM-dd"
const val YYYY_MM_DD_HH_MM: String = "yyyy-MM-dd HH:mm"
const val YYYY_MM_DD_HH_MM_SS: String = "yyyy-MM-dd HH:mm:ss"
const val HH_MM_SS: String = "HH:mm:ss"
}

View File

@ -0,0 +1,15 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
/**
* 邮箱消息
*/
@Data
object MailMessageConstant {
const val EMPTY_SEND_OBJECT: String = "空发送对象"
const val ADDRESS_NOT_NULL: String = "收件人不能为空"
const val TITLE_NOT_NULL: String = "标题不能为空"
const val SEND_MESSAGE_NOT_NULL: String = "发送消息不能为空"
const val EMAIL_CONFIG_NOT_FOUND: String = "邮箱配置为空"
}

View File

@ -0,0 +1,39 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
class MinioConstant {
companion object {
private const val FAVICON: String = "favicon"
private const val AVATAR: String = "avatar"
private const val ARTICLE: String = "article"
private const val CAROUSEL: String = "carousel"
private const val FEEDBACK: String = "feedback"
private const val ARTICLE_COVERS: String = "articleCovers"
private const val ARTICLE_ATTACHMENT: String = "articleAttachment"
private val typeMap: MutableMap<String, String> = HashMap()
init {
typeMap[FAVICON] = "/favicon/"
typeMap[AVATAR] = "/avatar/"
typeMap[ARTICLE] = "/article/"
typeMap[CAROUSEL] = "/carousel/"
typeMap[FEEDBACK] = "/feedback/"
typeMap["articleImages"] = "/articleImages/"
typeMap["articleVideo"] = "/articleVideo/"
typeMap[ARTICLE_COVERS] = "/articleCovers/"
typeMap[ARTICLE_ATTACHMENT] = "/articleAttachment/"
typeMap["images"] = "/images/"
typeMap["video"] = "/video/"
typeMap["default"] = "/default/"
}
@JvmStatic
fun getType(type: String): String {
val value = typeMap[type]
if (value != null) return value
throw RuntimeException(FileMessageConstant.STORAGE_OBJECT_EXCEPTION)
}
}
}

View File

@ -0,0 +1,75 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
/**
* Redis用户前缀设置
*/
@Data
class RedisUserConstant {
companion object {
// 管理员用户
const val ADMIN_LOGIN_INFO_PREFIX: String = "ADMIN::LOGIN_INFO::"
const val ADMIN_EMAIL_CODE_PREFIX: String = "ADMIN::EMAIL_CODE::"
// 普通用户
const val USER_LOGIN_INFO_PREFIX: String = "USER::LOGIN_INFO::"
const val USER_EMAIL_CODE_PREFIX: String = "USER::EMAIL_CODE::"
const val USER_DO_LIKE_PREFIX: String = "USER::doLike::"
/**
* * 管理员用户登录信息
*
* @param adminUser 管理员用户
* @return 登录信息key
*/
@JvmStatic
fun getAdminLoginInfoPrefix(adminUser: String): String {
return ADMIN_LOGIN_INFO_PREFIX + adminUser
}
/**
* * 管理员用户邮箱验证码
*
* @param adminUser 管理员用户
* @return 管理员用户邮箱验证码key
*/
@JvmStatic
fun getAdminUserEmailCodePrefix(adminUser: String): String {
return ADMIN_EMAIL_CODE_PREFIX + adminUser
}
/**
* * 用户登录信息
*
* @param user 用户名
* @return 登录信息key
*/
@JvmStatic
fun getUserLoginInfoPrefix(user: String): String {
return USER_LOGIN_INFO_PREFIX + user
}
/**
* * 用户邮箱验证码
*
* @param user 用户名
* @return 用户邮箱验证码key
*/
@JvmStatic
fun getUserEmailCodePrefix(user: String): String {
return USER_EMAIL_CODE_PREFIX + user
}
/**
* * 用户点赞操作
*
* @param user 用户名
* @return 用户点赞key
*/
@JvmStatic
fun getUserDoLikePrefix(user: String): String {
return USER_DO_LIKE_PREFIX + user
}
}
}

View File

@ -0,0 +1,11 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
class UserConstant {
companion object {
const val USER_AVATAR: String =
"https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132"
}
}

View File

@ -0,0 +1,34 @@
package cn.bunny.dao.pojo.email
import lombok.AllArgsConstructor
import lombok.Builder
import lombok.Data
import lombok.NoArgsConstructor
import org.springframework.web.multipart.MultipartFile
/**
* 邮件发送对象
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class EmailSend {
// 给谁发送
var sendTo: String? = null
// 发送主题
var subject: String? = null
// 是否为富文本
var isRichText: Boolean? = null
// 发送内容
var message: String? = null
// 抄送人
var ccParam: String? = null
// 发送的文件
var file: Array<MultipartFile>? = null
}

View File

@ -0,0 +1,20 @@
package cn.bunny.dao.pojo.email
import lombok.AllArgsConstructor
import lombok.Builder
import lombok.Data
import lombok.NoArgsConstructor
/**
* 邮箱发送初始化参数
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class EmailSendInit {
var port: Int? = null
var host: String? = null
var username: String? = null
var password: String? = null
}

View File

@ -0,0 +1,8 @@
package cn.bunny.dao.pojo.enums;
/**
* 数据库操作类型
*/
public enum OperationType {
UPDATE, INSERT
}

View File

@ -0,0 +1,18 @@
package cn.bunny.dao.pojo.file;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MinioFIlePath {
private String filename;
private String uuidFilename;
private String timeUuidFilename;
private String filepath;
private String bucketNameFilepath;
}

View File

@ -0,0 +1,27 @@
package cn.bunny.dao.pojo.result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PageResult<T> implements Serializable {
// 当前页
private Integer pageNo;
// 每页记录数
private Integer pageSize;
// 总记录数
private long total;
// 当前页数据集合
private List<T> list;
}

View File

@ -0,0 +1,173 @@
package cn.bunny.dao.pojo.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
// 状态码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
/**
* * 自定义返回体
*
* @param data 返回体
* @return Result<T>
*/
protected static <T> Result<T> build(T data) {
Result<T> result = new Result<>();
result.setData(data);
return result;
}
/**
* * 自定义返回体使用ResultCodeEnum构建
*
* @param body 返回体
* @param codeEnum 返回状态码
* @return Result<T>
*/
public static <T> Result<T> build(T body, ResultCodeEnum codeEnum) {
Result<T> result = build(body);
result.setCode(codeEnum.getCode());
result.setMessage(codeEnum.getMessage());
return result;
}
/**
* * 自定义返回体
*
* @param body 返回体
* @param code 返回状态码
* @param message 返回消息
* @return Result<T>
*/
public static <T> Result<T> build(T body, Integer code, String message) {
Result<T> result = build(body);
result.setCode(code);
result.setMessage(message);
result.setData(null);
return result;
}
/**
* * 操作成功
*
* @return Result<T>
*/
public static <T> Result<T> success() {
return success(null, ResultCodeEnum.SUCCESS);
}
/**
* * 操作成功
*
* @param data baseCategory1List
*/
public static <T> Result<T> success(T data) {
return build(data, ResultCodeEnum.SUCCESS);
}
/**
* * 操作成功-状态码
*
* @param codeEnum 状态码
*/
public static <T> Result<T> success(ResultCodeEnum codeEnum) {
return success(null, codeEnum);
}
/**
* * 操作成功-自定义返回数据和状态码
*
* @param data 返回体
* @param codeEnum 状态码
*/
public static <T> Result<T> success(T data, ResultCodeEnum codeEnum) {
return build(data, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param message 错误信息
*/
public static <T> Result<T> success(T data, String message) {
return build(data, 200, message);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param code 状态码
* @param message 错误信息
*/
public static <T> Result<T> success(T data, Integer code, String message) {
return build(data, code, message);
}
/**
* * 操作失败
*/
public static <T> Result<T> error() {
return Result.build(null);
}
/**
* * 操作失败-自定义返回数据
*
* @param data 返回体
*/
public static <T> Result<T> error(T data) {
return build(data, ResultCodeEnum.FAIL);
}
/**
* * 操作失败-状态码
*
* @param codeEnum 状态码
*/
public static <T> Result<T> error(ResultCodeEnum codeEnum) {
return build(null, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param codeEnum 状态码
*/
public static <T> Result<T> error(T data, ResultCodeEnum codeEnum) {
return build(data, codeEnum);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param code 状态码
* @param message 错误信息
*/
public static <T> Result<T> error(T data, Integer code, String message) {
return build(data, code, message);
}
/**
* * 操作失败-自定义返回数据和状态码
*
* @param data 返回体
* @param message 错误信息
*/
public static <T> Result<T> error(T data, String message) {
return build(null, 500, message);
}
}

View File

@ -0,0 +1,54 @@
package cn.bunny.dao.pojo.result;
import lombok.Getter;
/**
* 统一返回结果状态信息类
*/
@Getter
public enum ResultCodeEnum {
// 成功操作 200
SUCCESS(200, "操作成功"),
SUCCESS_LOGOUT(200, "退出成功"),
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
// 验证错误 201
USERNAME_NOT_EMPTY(201, "用户名不能为空"),
PASSWORD_NOT_EMPTY(201, "密码不能为空"),
EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"),
SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"),
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
LOGIN_ERROR(201, "账号或密码错误"),
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
// 数据相关 206
ILLEGAL_REQUEST(206, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
DATA_ERROR(206, "数据异常"),
// 身份过期 208
LOGIN_AUTH(208, "请先登陆"),
AUTHENTICATION_EXPIRED(208, "身份验证过期"),
SESSION_EXPIRATION(208, "会话过期"),
// 封禁 209
FAIL_NO_ACCESS_DENIED_USER_LOCKED(209, "该账户被封禁"),
THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"),
// 提示错误
URL_ENCODE_ERROR(216, "URL编码失败"),
ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
// 无权访问 403
FAIL_REQUEST_NOT_AUTH(403, "用户未认证"),
FAIL_NO_ACCESS_DENIED(403, "无权访问"),
FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"),
LOGGED_IN_FROM_ANOTHER_DEVICE(403, "没有权限访问"),
// 系统错误 500
SERVICE_ERROR(500, "服务异常"),
FAIL(500, "失败"),
;
private final Integer code;
private final String message;
ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -0,0 +1,45 @@
package cn.bunny.dao.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.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseVo implements Serializable {
@JsonProperty("id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long id;
@ApiModelProperty("更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;
@ApiModelProperty("发布时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createTime;
@ApiModelProperty("操作用户ID")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long updateUser;
@ApiModelProperty("创建用户")
@JsonFormat(shape = JsonFormat.Shape.STRING)
@JSONField(serializeUsing = ToStringSerializer.class)
private Long createUser;
}

View File

@ -0,0 +1,19 @@
package cn.bunny.dao.vo.email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EmailTemplateVo {
// 模板名称
private String templateName;
// 主题
private String subject;
// 邮件内容
private String body;
}

View File

@ -0,0 +1,66 @@
package cn.bunny.dao.vo.system.login;
import cn.bunny.dao.vo.BaseVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import java.util.List;
/**
* 用户登录返回内容
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LoginVo extends BaseVo {
@ApiModelProperty("昵称")
private String nickName;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("手机号")
private String phone;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("头像")
private String avatar;
@ApiModelProperty("0:女 1:男")
private Byte sex;
@ApiModelProperty("个人描述")
private String personDescription;
@ApiModelProperty("文章显示模式")
private String articleMode;
@ApiModelProperty("页面布局方式")
private String layout;
@ApiModelProperty("最后登录IP")
private String lastLoginIp;
@ApiModelProperty("最后登录ip地址")
private String lastLoginIpAddress;
@ApiModelProperty("积分")
private Integer totalIntegral;
@ApiModelProperty("当前积分")
private Integer currentIntegral;
@ApiModelProperty("0:禁用 1:正常")
private Boolean status;
private String token;
private List<String> roleList;
private List<String> powerList;
}

View File

@ -0,0 +1,20 @@
package cn.bunny.dao.vo.system.login;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "验证码响应结果实体类")
public class ValidateCodeVo {
@Schema(description = "验证码key")
private String codeKey;
@Schema(description = "验证码value")
private String codeValue;
}

View File

@ -0,0 +1,40 @@
package cn.bunny.dao.vo.system.user;
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 获取用户信息返回参数
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserInfoVo {
private String nickName;
private String email;
private String avatar;
private Byte sex;
private Byte status;
private String personDescription;
@JsonFormat(pattern = LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime joinTime;
private String lastLoginIpAddress;
private Integer currentIntegral;
private String token;
private List<String> roles;
private List<String> powerCodeList;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,30 @@
<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>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-mail</artifactId>
<packaging>jar</packaging>
<name>module-mail</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
</build>
</project>

View File

@ -0,0 +1,21 @@
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,23 @@
package cn.bunny.module.mail.utils;
import cn.bunny.common.service.utils.EmptyUtil;
import cn.bunny.dao.pojo.constant.MailMessageConstant;
import cn.bunny.dao.pojo.email.EmailSend;
public class MailSendCheckUtil {
/**
* 检测发送对象是否为空的对象
*
* @param emailSend 邮件发送对象
*/
public static void check(EmailSend emailSend) {
// 空发送对象
EmptyUtil.isEmpty(emailSend, MailMessageConstant.EMPTY_SEND_OBJECT);
// 收件人不能为空
EmptyUtil.isEmpty(emailSend.getSendTo(), MailMessageConstant.ADDRESS_NOT_NULL);
// 标题不能为空
EmptyUtil.isEmpty(emailSend.getSubject(), MailMessageConstant.TITLE_NOT_NULL);
// 发送消息不能为空
EmptyUtil.isEmpty(emailSend.getMessage(), MailMessageConstant.SEND_MESSAGE_NOT_NULL);
}
}

View File

@ -0,0 +1,167 @@
package cn.bunny.module.mail.utils;
import cn.bunny.dao.pojo.email.EmailSend;
import cn.bunny.dao.pojo.email.EmailSendInit;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.SimpleMailMessage;
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 {
private final String username;
private final JavaMailSenderImpl javaMailSender;
/**
* 初始化构造函数进行当前类赋值
*/
public MailSenderUtil(EmailSendInit emailSendInit) {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(emailSendInit.getHost());
javaMailSender.setPort(emailSendInit.getPort());
javaMailSender.setUsername(emailSendInit.getUsername());
javaMailSender.setPassword(emailSendInit.getPassword());
javaMailSender.setProtocol("smtps");
javaMailSender.setDefaultEncoding("UTF-8");
this.username = emailSendInit.getUsername();
this.javaMailSender = javaMailSender;
}
/**
* 综合邮箱发送
*
* @param emailSend 邮件消息
*/
public void sendEmail(EmailSend emailSend) throws MessagingException {
MailSendCheckUtil.check(emailSend);
// 创建 MimeMessage 对象用于发送邮件富文本或者附件
MimeMessage message = javaMailSender.createMimeMessage();
// 创建 MimeMessageHelper
MimeMessageHelper helper = new MimeMessageHelper(message, true);
// 设置发送人
helper.setFrom(username);
// 设置邮件接受者
helper.setTo(emailSend.getSendTo());
// 设置邮件主题
helper.setSubject(emailSend.getSubject());
// 设置发送消息 为富文本
helper.setText(emailSend.getMessage(), emailSend.isRichText());
// 设置抄送人
helper.setCc(emailSend.getCcParam().split(","));
// 邮件添加附件
MultipartFile[] files = emailSend.getFile();
for (MultipartFile file : files) {
helper.addAttachment(Objects.requireNonNull(file.getOriginalFilename()), file);
}
// 发送邮件
javaMailSender.send(message);
}
/**
* 发送邮件-简单
*
* @param emailSend 邮件消息
*/
public void sendSimpleEmail(EmailSend emailSend) {
MailSendCheckUtil.check(emailSend);
// 创建邮件消息体 SimpleMailMessage 发送简单邮件
SimpleMailMessage mailMessage = new SimpleMailMessage();
// 设置邮件发送人
mailMessage.setFrom(username);
// 设置邮件接受者
mailMessage.setTo(emailSend.getSendTo());
// 设置邮件主题
mailMessage.setSubject(emailSend.getSubject());
// 设置邮件消息
mailMessage.setText(emailSend.getMessage());
javaMailSender.send(mailMessage);
}
/**
* 发送带附件邮件
*
* @param emailSend 邮件消息
*/
public void sendAttachmentEmail(EmailSend emailSend, MultipartFile file, boolean isRich) throws MessagingException {
MailSendCheckUtil.check(emailSend);
// 创建 MimeMessage 对象用户发送附件或者是富文本内容
MimeMessage mailMessage = javaMailSender.createMimeMessage();
// 创建 MimeMessageHelper
MimeMessageHelper helper = new MimeMessageHelper(mailMessage, true);
// 奢姿邮件发送人
helper.setFrom(username);
// 设置邮件接受者
helper.setTo(emailSend.getSendTo());
// 设置邮件消息
helper.setText(emailSend.getMessage(), isRich);
// 设置邮件主题
helper.setSubject(emailSend.getSubject());
// 邮件添加附件
helper.addAttachment(Objects.requireNonNull(file.getOriginalFilename()), file);
// 发送邮件
javaMailSender.send(mailMessage);
}
/**
* 发送富文本邮件
*
* @param emailSend 邮件消息
*/
public void sendRichText(EmailSend emailSend, boolean isRich) throws MessagingException {
MailSendCheckUtil.check(emailSend);
// 创建 MimeMessage 对象用户发送附件或者是富文本内容
MimeMessage mailMessage = javaMailSender.createMimeMessage();
// 创建 MimeMessageHelper
MimeMessageHelper helper = new MimeMessageHelper(mailMessage, true);
// 设置邮件发送者
helper.setFrom(username);
// 设置邮件接受者
helper.setTo(emailSend.getSendTo());
// 设置邮件主题
helper.setSubject(emailSend.getSubject());
// 设置邮件富文本后面跟true 表示HTML格式发送
helper.setText(emailSend.getMessage(), isRich);
// 发送邮件
javaMailSender.send(mailMessage);
}
/**
* 发送带抄送的邮件
*
* @param emailSend 邮件消息
*/
public void sendCC(EmailSend emailSend, boolean isRich) throws MessagingException {
MailSendCheckUtil.check(emailSend);
// 创建 MimeMessage 对象用于发送邮件富文本或者附件
MimeMessage message = javaMailSender.createMimeMessage();
// 创建 MimeMessageHelper
MimeMessageHelper helper = new MimeMessageHelper(message, true);
// 设置发送人
helper.setFrom(username);
// 设置邮件接受者
helper.setTo(emailSend.getSendTo());
// 设置邮件主题
helper.setSubject(emailSend.getSubject());
// 设置发送消息 为富文本
helper.setText(emailSend.getMessage(), isRich);
// 设置抄送人
helper.setCc(emailSend.getCcParam().split(","));
// 发送邮件
javaMailSender.send(message);
}
}

View File

@ -0,0 +1,31 @@
<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>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-minio</artifactId>
<packaging>jar</packaging>
<name>module-minio</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
</build>
</project>

View File

@ -0,0 +1,26 @@
package cn.bunny.module.minio.properties;
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,143 @@
package cn.bunny.module.minio.utils;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.dao.pojo.constant.FileMessageConstant;
import cn.bunny.dao.pojo.constant.MinioConstant;
import cn.bunny.dao.pojo.file.MinioFIlePath;
import cn.bunny.module.minio.properties.MinioProperties;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
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.UUID;
/**
* Minio操作工具类 简化操作步骤
* ByBunny0212
*/
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioProperties properties;
@Autowired
private MinioClient minioClient;
/**
* 获取Minio文件路径
*/
public static MinioFIlePath getMinioFilePath(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 getUploadMinioObjectFilePath(MultipartFile file, String minioPreType) throws IOException {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
if (file != null) {
MinioFIlePath minioFile = getMinioFilePath(bucketName, minioPreType, file);
String filepath = minioFile.getFilepath();
// 上传对象
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
// 设置图片地址
return minioFile;
}
return null;
}
/**
* 获取默认bucket文件并返回字节数组
*
* @param objectName 对象名称
* @return 文件流对象
*/
public byte[] getBucketObjectByte(String objectName) {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
try {
objectName = objectName.replace("/" + bucketName, "");
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
return getObjectResponse.readAllBytes();
} catch (Exception exception) {
exception.getStackTrace();
}
throw new BunnyException(FileMessageConstant.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) {
exception.getStackTrace();
throw new BunnyException(FileMessageConstant.FILE_UPLOAD_EXCEPTION);
}
}
}

View File

@ -0,0 +1,39 @@
<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>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module-rabbitMQ</artifactId>
<packaging>jar</packaging>
<name>module-rabbitMQ</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.0-rc1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
</build>
</project>

View File

@ -0,0 +1,38 @@
package cn.bunny.module.rabbitMQ.config;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RabbitMqConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setReturnsCallback(returned -> {
log.error("触发return callback,");
log.debug("exchange: {}", returned.getExchange());
log.debug("routingKey: {}", returned.getRoutingKey());
log.debug("message: {}", returned.getMessage());
log.debug("replyCode: {}", returned.getReplyCode());
log.debug("replyText: {}", returned.getReplyText());
});
}
@Bean
public MessageConverter messageConverter() {
// 1.定义消息转换器
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
// 2.配置自动创建消息id用于识别不同消息也可以在业务中基于ID判断是否是重复消息
converter.setCreateMessageIds(true);
return converter;
}
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DelayConsumer {
}

View File

@ -0,0 +1,24 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DirectConsumer {
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 发送红色消息
*/
@Test
void testSendDirectRed() throws Exception {
for (int i = 0; i < 1000; i++) {
rabbitTemplate.convertAndSend("bunny.direct", "red", "发送消息:" + i);
}
}
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ErrorConsumer {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FanoutConsumer {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LazyConsumer {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TopicConsumer {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DelayListener {
}

View File

@ -0,0 +1,30 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class DirectListener {
/**
* * 监听者2
* 创建队列 持久化的不自动删除
* 创建交换机 持久化的不自动删除
* key包含 red yellow
*
* @param message 接受消息
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2", durable = "true", autoDelete = "false"),
exchange = @Exchange(name = "bunny.direct", type = ExchangeTypes.DIRECT, durable = "true", autoDelete = "false"),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String message) {
System.out.println("消费者2接收到 Direct key 为 {\"red\", \"yellow\"} 消息:【" + message + "");
}
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class ErrorListener {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class FanoutListener {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LazyListener {
}

View File

@ -0,0 +1,9 @@
package cn.bunny.module.rabbitMQ.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class TopicListener {
}

114
module/pom.xml Normal file
View File

@ -0,0 +1,114 @@
<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>BunnyBBS-server-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>module</artifactId>
<packaging>pom</packaging>
<name>module</name>
<url>https://maven.apache.org</url>
<modules>
<module>module-minio</module>
<module>module-rabbitMQ</module>
<module>spring-security</module>
<module>module-mail</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>common-service</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.27.2</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,36 @@
<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>module</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-security</artifactId>
<packaging>jar</packaging>
<name>spring-security</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- spring-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
</build>
</project>

View File

@ -0,0 +1,101 @@
package cn.bunny.security.config
import cn.bunny.security.custom.CustomPasswordEncoder
import cn.bunny.security.filter.TokenAuthenticationFilter
import cn.bunny.security.filter.TokenLoginFilterService
import cn.bunny.security.handelr.SecurityAccessDeniedHandler
import cn.bunny.security.handelr.SecurityAuthenticationEntryPoint
import cn.bunny.security.service.CustomAuthorizationManagerService
import cn.bunny.security.service.CustomUserDetailsService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
import org.springframework.security.config.annotation.web.configurers.*
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.util.matcher.RegexRequestMatcher
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
open class WebSecurityConfig {
@Autowired
private val redisTemplate: RedisTemplate<Any, Any>? = null
// 自定义用户接口
@Autowired
private val customUserDetailsService: CustomUserDetailsService? = null
// 自定义密码加密器
@Autowired
private val customPasswordEncoder: CustomPasswordEncoder? = null
// 自定义验证码
@Autowired
private val customAuthorizationManager: CustomAuthorizationManagerService? = null
@Autowired
private val authenticationConfiguration: AuthenticationConfiguration? = null
@Bean
@Throws(Exception::class)
open fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity // 前端段分离不需要---禁用明文验证
.httpBasic { obj: HttpBasicConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---禁用默认登录页
.formLogin { obj: FormLoginConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---禁用退出页
.logout { obj: LogoutConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---csrf攻击
.csrf { obj: CsrfConfigurer<HttpSecurity> -> obj.disable() } // 跨域访问权限,如果需要可以关闭后自己配置跨域访问
.cors { obj: CorsConfigurer<HttpSecurity> -> obj.disable() } // 前后端分离不需要---因为是无状态的
.sessionManagement { obj: SessionManagementConfigurer<HttpSecurity> -> obj.disable() } // 前后端分离不需要---记住我e -> e.rememberMeParameter("rememberBunny").rememberMeCookieName("rememberBunny").key("BunnyKey")
.rememberMe { obj: RememberMeConfigurer<HttpSecurity> -> obj.disable() }
.authorizeHttpRequests { authorize ->
// 有样式文件,不需要访问权限
authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^\\S*[css|js]$")).permitAll()
// 上面都不是需要鉴权访问
authorize.anyRequest().access(customAuthorizationManager)
}
.exceptionHandling { exception: ExceptionHandlingConfigurer<HttpSecurity?> ->
// 请求未授权接口
exception.authenticationEntryPoint(SecurityAuthenticationEntryPoint())
// 没有权限访问
exception.accessDeniedHandler(SecurityAccessDeniedHandler())
} // 登录验证过滤器
.addFilterBefore(
TokenLoginFilterService(authenticationConfiguration!!, redisTemplate!!, customUserDetailsService!!),
UsernamePasswordAuthenticationFilter::class.java
) // 其它权限鉴权过滤器
.addFilterAt(
TokenAuthenticationFilter(redisTemplate),
UsernamePasswordAuthenticationFilter::class.java
) // 自定义密码加密器和用户登录
.passwordManagement(customPasswordEncoder).userDetailsService(customUserDetailsService)
return httpSecurity.build()
}
@Bean
open fun sessionRegistry(): SessionRegistry {
return SessionRegistryImpl()
}
// 排出鉴定路径
@Bean
open fun webSecurityCustomizer(): WebSecurityCustomizer {
val annotations = arrayOf(
"/", "/test/**",
"/*/*/noAuth/**", "/*/noAuth/**", "/noAuth/**", "/ws/**",
"/favicon.ico", "*.html",
"/swagger-resources/**", "/v3/**", "/swagger-ui/**"
)
return WebSecurityCustomizer { web: WebSecurity -> web.ignoring().requestMatchers(*annotations) }
}
}

View File

@ -0,0 +1,25 @@
package cn.bunny.security.custom
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.PasswordManagementConfigurer
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.util.DigestUtils
/**
* 自定义密码加密比对
*/
@Configuration
open class CustomPasswordEncoder : PasswordEncoder, Customizer<PasswordManagementConfigurer<HttpSecurity?>?> {
override fun encode(rawPassword: CharSequence): String {
return DigestUtils.md5DigestAsHex(rawPassword.toString().toByteArray())
}
override fun matches(rawPassword: CharSequence, encodedPassword: String): Boolean {
return encodedPassword.matches(DigestUtils.md5DigestAsHex(rawPassword.toString().toByteArray()).toRegex())
}
override fun customize(httpSecurityPasswordManagementConfigurer: PasswordManagementConfigurer<HttpSecurity?>?) {
}
}

View File

@ -0,0 +1,16 @@
package cn.bunny.security.custom
import cn.bunny.dao.entity.system.user.User
import lombok.Getter
import lombok.Setter
import org.springframework.security.core.GrantedAuthority
/**
* 重写自带的User
*/
@Getter
@Setter
class CustomUser(private val user: User, authorities: Collection<GrantedAuthority?>?) :
org.springframework.security.core.userdetails.User(
user.email, user.password, authorities
)

View File

@ -0,0 +1,100 @@
package cn.bunny.security.filter
import cn.bunny.common.service.context.BaseContext.Companion.setLoginVo
import cn.bunny.common.service.context.BaseContext.Companion.setUserId
import cn.bunny.common.service.context.BaseContext.Companion.setUsername
import cn.bunny.common.service.exception.BunnyException
import cn.bunny.common.service.utils.JwtHelper.getUserId
import cn.bunny.common.service.utils.JwtHelper.getUsername
import cn.bunny.common.service.utils.ResponseUtil.Companion.out
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminLoginInfoPrefix
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminUserEmailCodePrefix
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import cn.bunny.dao.vo.system.login.LoginVo
import com.alibaba.fastjson2.JSON
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.util.StringUtils
import org.springframework.web.filter.OncePerRequestFilter
import java.io.IOException
import java.util.*
import java.util.function.Consumer
class TokenAuthenticationFilter(private val redisTemplate: RedisTemplate<Any, Any>) : OncePerRequestFilter() {
@Throws(ServletException::class, IOException::class, BunnyException::class)
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
val token = request.getHeader("token")
// login请求就没token直接放行因为后边有其他的过滤器
if (token == null) {
doFilter(request, response, chain)
return
}
// 如果想让这个用户下线清空Redis这个用户值返回未登录判断Redis是否有这个用户
// 如果想让这个用户锁定清空Redis值并在数据库中设置status值为1
val userName = getUsername(token) ?: return
val usernameObject = redisTemplate.opsForValue()[getAdminLoginInfoPrefix(userName)]
if (usernameObject == null) {
val error = Result.error<Any?>(ResultCodeEnum.LOGIN_AUTH)
out(response, error)
return
}
// 获取Redis中登录信息
val loginVo = JSON.parseObject(JSON.toJSONString(usernameObject), LoginVo::class.java)
// 如果是登录接口,直接放行
val authentication = getAuthentication(request)
if (authentication != null) {
// 设置用户详细信息
authentication.details = loginVo.personDescription
SecurityContextHolder.getContext().authentication = authentication
}
chain.doFilter(request, response)
}
/**
* * 用户请求判断
*
* @param request 请求
* @return 验证码方法
*/
private fun getAuthentication(request: HttpServletRequest): UsernamePasswordAuthenticationToken? {
// 请求头是否有token
val token = request.getHeader("token")
val username = getAdminLoginInfoPrefix(Objects.requireNonNull(getUsername(token))!!)
val authList: MutableList<SimpleGrantedAuthority> = ArrayList()
if (!StringUtils.hasText(username)) return null
// 当前用户信息放到ThreadLocal里面
val redisUserinfo = redisTemplate.opsForValue()[getAdminUserEmailCodePrefix(username)]
val loginAdminVo = JSON.parseObject(JSON.toJSONString(redisUserinfo), LoginVo::class.java)
val userId = getUserId(token)
if (userId != null) {
setUserId(userId)
}
setUsername(username)
setLoginVo(loginAdminVo)
// 通过username从redis获取权限数据
val userObject = redisTemplate.opsForValue()[username]
// 把redis获取字符串权限数据转换要求集合类型 List<SimpleGrantedAuthority>
if (userObject != null) {
val loginVo = JSON.parseObject(JSON.toJSONString(userObject), LoginVo::class.java)
val roleList = loginVo.roleList
roleList.forEach(Consumer { role: String? -> authList.add(SimpleGrantedAuthority(role)) })
return UsernamePasswordAuthenticationToken(username, null, authList)
} else {
return UsernamePasswordAuthenticationToken(username, null, ArrayList())
}
}
}

View File

@ -0,0 +1,144 @@
package cn.bunny.security.filter
import cn.bunny.common.service.utils.ResponseUtil.Companion.out
import cn.bunny.dao.dto.user.LoginDto
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminLoginInfoPrefix
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminUserEmailCodePrefix
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import cn.bunny.security.handelr.SecurityAuthenticationFailureHandler
import cn.bunny.security.handelr.SecurityAuthenticationSuccessHandler
import cn.bunny.security.service.CustomUserDetailsService
import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.util.StringUtils
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit
/**
* * UsernamePasswordAuthenticationFilter
* 也可以在这里添加验证码短信等的验证
* 由于SpringSecurity的登录只能是表单形式 并且用户名密码需要时usernamepassword,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数
* 再去设置到 UsernamePasswordAuthenticationToken 来改变请求传参方式参数名等 或者也可以在登录的时候加入其他参数等等
*/
class TokenLoginFilterService(
authenticationConfiguration: AuthenticationConfiguration,
redisTemplate: RedisTemplate<Any, Any>,
customUserDetailsService: CustomUserDetailsService
) : UsernamePasswordAuthenticationFilter() {
private val redisTemplate: RedisTemplate<Any, Any>
private val customUserDetailsService: CustomUserDetailsService
private lateinit var loginDto: LoginDto
// 依赖注入
init {
this.setAuthenticationSuccessHandler(SecurityAuthenticationSuccessHandler())
this.setAuthenticationFailureHandler(SecurityAuthenticationFailureHandler())
this.setPostOnly(false)
// ? 指定登录接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(AntPathRequestMatcher("/*/login", HttpMethod.POST.name()))
this.authenticationManager = authenticationConfiguration.authenticationManager
// 依赖注入
this.redisTemplate = redisTemplate
this.customUserDetailsService = customUserDetailsService
}
/**
* * 登录认证获取输入的用户名和密码调用方法认证
* 接受前端login登录参数
* 在这里可以设置短信验证登录
*/
@Throws(AuthenticationException::class)
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication? {
try {
// 获取用户信息
loginDto = ObjectMapper().readValue(request.inputStream, LoginDto::class.java)
// 登录验证码判断
val username = loginDto.username
val emailCode = loginDto.emailCode.lowercase(Locale.getDefault())
val redisEmailCode = redisTemplate.opsForValue().get(getAdminUserEmailCodePrefix(username))
// 如果不存在验证码
if (!StringUtils.hasText(emailCode)) {
out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_EMPTY))
return null
}
if (redisEmailCode == null) {
out(response, Result.error(ResultCodeEnum.SEND_EMAIL_CODE_NOT_EMPTY))
return null
}
// 验证码不匹配
if ((redisEmailCode as String).lowercase(Locale.getDefault()) != emailCode) {
out(response, Result.error(ResultCodeEnum.EMAIL_CODE_NOT_MATCHING))
return null
}
// 封装对象,将用户名密码传入
val authenticationToken: Authentication =
UsernamePasswordAuthenticationToken(loginDto.username, loginDto.password)
return authenticationManager.authenticate(authenticationToken)
} catch (e: IOException) {
throw RuntimeException(e.localizedMessage)
}
}
/**
* * 认证成功调用方法
* 返回登录成功后的信息
*/
override fun successfulAuthentication(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain,
auth: Authentication
) {
// 封装返回对象
val loginAdminVo = customUserDetailsService.login(loginDto)
// 判断用户是否被锁定
if (loginAdminVo.status) {
out(response, Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED))
return
}
// 将值存入Redis中
redisTemplate.opsForValue()[getAdminLoginInfoPrefix(loginAdminVo.email), loginAdminVo, 15] =
TimeUnit.DAYS
// 将Redis中验证码删除
redisTemplate.delete(getAdminUserEmailCodePrefix(loginAdminVo.email))
// 返回登录信息
out(response, Result.success(loginAdminVo))
}
/**
* * 认证失败调用方法失败判断
* 1. 是否包含用户名
* 2. 是否包含密码
*/
override fun unsuccessfulAuthentication(
request: HttpServletRequest,
response: HttpServletResponse,
failed: AuthenticationException
) {
when {
loginDto == null -> out(response, Result.error(ResultCodeEnum.LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY))
loginDto.username.isNullOrBlank() -> out(response, Result.error(ResultCodeEnum.USERNAME_NOT_EMPTY))
loginDto.password.isNullOrBlank() -> out(response, Result.error(ResultCodeEnum.PASSWORD_NOT_EMPTY))
else -> out(response, Result.error(null, ResultCodeEnum.LOGIN_ERROR))
}
}
}

View File

@ -0,0 +1,30 @@
package cn.bunny.security.handelr
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import com.alibaba.fastjson2.JSON
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import lombok.SneakyThrows
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.web.access.AccessDeniedHandler
/**
* 没有权限访问
*/
class SecurityAccessDeniedHandler : AccessDeniedHandler {
@SneakyThrows
override fun handle(
request: HttpServletRequest,
response: HttpServletResponse,
accessDeniedException: AccessDeniedException
) {
val result = Result.error<Any>(ResultCodeEnum.FAIL_NO_ACCESS_DENIED)
val json = JSON.toJSON(result)
// 返回响应
response.contentType = "application/json;charset=UTF-8"
response.writer.println(json)
}
}

View File

@ -0,0 +1,42 @@
package cn.bunny.security.handelr
import cn.bunny.common.service.utils.ResponseUtil.Companion.out
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.apache.logging.log4j.LogManager
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.util.StringUtils
import java.io.IOException
/**
* 请求未认证接口
*/
class SecurityAuthenticationEntryPoint : AuthenticationEntryPoint {
private val logger = LogManager.getLogger(SecurityAuthenticationEntryPoint::class.java)
@Throws(IOException::class)
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException
) {
val token = response.getHeader("token")
val message = authException.message
// 创建结果对象
val result: Result<Any?>
if (StringUtils.hasText(token)) {
result = Result.error(ResultCodeEnum.LOGIN_AUTH)
logger.info("请求未登录接口:{}用户id{}", message, null)
} else {
result = Result.error(ResultCodeEnum.LOGGED_IN_FROM_ANOTHER_DEVICE)
logger.info("请求未授权接口:{}用户id{}", message, token)
}
// 返回响应
out(response, result)
}
}

View File

@ -0,0 +1,30 @@
package cn.bunny.security.handelr
import cn.bunny.dao.pojo.result.Result
import com.alibaba.fastjson2.JSON
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import java.io.IOException
class SecurityAuthenticationFailureHandler : AuthenticationFailureHandler {
@Throws(IOException::class, ServletException::class)
override fun onAuthenticationFailure(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException
) {
// 错误消息
val localizedMessage = exception.localizedMessage
val result = Result.error(localizedMessage)
// 转成JSON
val json = JSON.toJSON(result)
// 返回响应
response.contentType = "application/json;charset=UTF-8"
response.writer.println(json)
}
}

View File

@ -0,0 +1,29 @@
package cn.bunny.security.handelr
import cn.bunny.dao.pojo.result.Result
import com.alibaba.fastjson2.JSON
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import java.io.IOException
/**
* 登录成功
*/
class SecurityAuthenticationSuccessHandler : AuthenticationSuccessHandler {
@Throws(IOException::class)
override fun onAuthenticationSuccess(
request: HttpServletRequest,
response: HttpServletResponse,
authentication: Authentication
) {
// 获取用户身份信息
val principal = authentication.principal
val result = Result.success(principal)
// 返回
response.contentType = "application/json;charset=UTF-8"
response.writer.println(JSON.toJSON(result))
}
}

View File

@ -0,0 +1,6 @@
package cn.bunny.security.service
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
interface CustomAuthorizationManagerService : AuthorizationManager<RequestAuthorizationContext?>

View File

@ -0,0 +1,23 @@
package cn.bunny.security.service
import cn.bunny.dao.dto.user.LoginDto
import cn.bunny.dao.vo.system.login.LoginVo
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
interface CustomUserDetailsService : UserDetailsService {
/**
* 根据用户名获取用户对象获取不到直接抛异常
*/
@Throws(UsernameNotFoundException::class)
override fun loadUserByUsername(username: String): UserDetails
/**
* 前台用户登录接口
*
* @param loginDto 登录参数
* @return 登录后结果返回
*/
fun login(loginDto: LoginDto?): LoginVo
}

199
pom.xml Normal file
View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/>
</parent>
<groupId>cn.bunny</groupId>
<artifactId>BunnyBBS-server-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>bunny-template</name>
<description>bunny-template</description>
<modules>
<module>services</module>
<module>common</module>
<module>dao</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<junit.version>3.8.1</junit.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mysql.version>8.0.30</mysql.version>
<knife4j.version>4.5.0</knife4j.version>
<fastjson2.version>2.0.47</fastjson2.version>
<minio.version>8.5.9</minio.version>
<lombok.version>1.18.32</lombok.version>
<jwt.version>0.9.1</jwt.version>
<easyexcel.version>3.3.3</easyexcel.version>
<jodatime.version>2.10.1</jodatime.version>
<aspectj>1.9.21</aspectj>
<pagehelper.version>6.1.0</pagehelper.version>
<velocity.version>2.2</velocity.version>
<velocity-tools.version>3.1</velocity-tools.version>
<HikariCP.version>5.1.0</HikariCP.version>
<dynamic.datasource.version>4.3.1</dynamic.datasource.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity.tools</groupId>
<artifactId>velocity-tools-generic</artifactId>
<version>${velocity-tools.version}</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mysql连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${HikariCP.version}</version>
</dependency>
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic.datasource.version}</version>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.27</version>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- Excel表操作 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${jodatime.version}</version>
</dependency>
<!-- fasterxml -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.12.3</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>${maven.compiler.target}</jvmTarget>
</configuration>
</plugin>
</plugins>
</build>
</project>

21
services/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM openjdk:17
MAINTAINER bunny
#系统编码
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
# 设置时区,构建镜像时执行的命令
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
# 设定工作目录
WORKDIR /home/bunny
# 复制jar包
COPY target/*.jar /home/bunny/app.jar
#启动容器时的进程
ENTRYPOINT ["java","-jar","/home/bunny/app.jar"]
#暴露 8800 端口
EXPOSE 8800

148
services/pom.xml Normal file
View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.bunny</groupId>
<artifactId>services</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>services</name>
<description>services</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>common-service</artifactId>
<version>1.0.0</version>
</dependency>
<!-- rabbitMQ -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>module-rabbitMQ</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- security -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- mail模块 -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>module-mail</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- spring-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- asp 切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<source>src/main/kotlin</source>
<source>target/generated-sources/annotations</source>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
<jvmTarget>17</jvmTarget>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

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