commit 0c7c85248e9bfc4ee3b1856d34cf2471258c66e1 Author: bunny <1319900154@qq.com> Date: Fri Sep 13 15:14:58 2024 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f18fff --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..3a4cd05 --- /dev/null +++ b/ReadMe.md @@ -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 implements Serializable { + // 当前页 + private Integer pageNo; + // 每页记录数 + private Integer pageSize; + // 总记录数 + private long total; + // 当前页数据集合 + private List list; +} +``` + +以及常用的枚举状态码(展示部分) + +![image-20240822092510151](./images/image-20240822092510151.png) + +### 多数据库源配置 + +开发中有时会使用到多个数据库源,这个配置也是来自MybatisPlus官方推荐的库 + +```xml + + + com.baomidou + dynamic-datasource-spring-boot3-starter + 4.3.1 + +``` + +#### 配置简介 + +如果不需要多数据库,移除包之后将注释的部分放开,删除`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); +} +``` + diff --git a/common/common-generator/pom.xml b/common/common-generator/pom.xml new file mode 100644 index 0000000..1878cd5 --- /dev/null +++ b/common/common-generator/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + cn.bunny + common + 1.0.0 + + + common-generator + + + + javax.xml.bind + jaxb-api + 2.1 + + + + com.baomidou + mybatis-plus-generator + 3.5.6 + + + org.apache.velocity + velocity-engine-core + 2.3 + + + + + src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + \ No newline at end of file diff --git a/common/common-generator/src/main/kotlin/cn/bunny/common/generator/NewCodeGet.kt b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/NewCodeGet.kt new file mode 100644 index 0000000..944644c --- /dev/null +++ b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/NewCodeGet.kt @@ -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) { + 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 customFile = new HashMap<>(); + // // 配置DTO(需要的话)但是需要有能配置Dto的模板引擎,比如freemarker,但是这里我们用的VelocityEngine,因此不多作介绍 + // customFile.put(outputDir, "/src/main/resources/templates/entityDTO.java.ftl"); + // consumer.customFile(customFile); + // }) + .execute() + } + } +} diff --git a/common/common-service/pom.xml b/common/common-service/pom.xml new file mode 100644 index 0000000..6a0476f --- /dev/null +++ b/common/common-service/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + cn.bunny + common + 1.0.0 + + + common-service + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.redisson + redisson + 3.26.1 + + + + org.lionsoul + ip2region + 2.6.5 + + + + + src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + \ No newline at end of file diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/config/Knife4jConfig.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/Knife4jConfig.kt new file mode 100644 index 0000000..15860dc --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/Knife4jConfig.kt @@ -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() + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MyBatisPlusFieldConfig.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MyBatisPlusFieldConfig.kt new file mode 100644 index 0000000..5e27b2a --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MyBatisPlusFieldConfig.kt @@ -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()) + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MybatisPlusConfig.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MybatisPlusConfig.kt new file mode 100644 index 0000000..c429e76 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/MybatisPlusConfig.kt @@ -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 + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/config/RedisConfiguration.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/RedisConfiguration.kt new file mode 100644 index 0000000..93e3996 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/RedisConfiguration.kt @@ -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 { + val redisTemplate = RedisTemplate() + 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 { + 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-dd’T’HH:mm:ss.SSS + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + mapper.registerModule(timeModule) + + return Jackson2JsonRedisSerializer(mapper, Any::class.java) + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/config/WebMvcConfiguration.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/WebMvcConfiguration.kt new file mode 100644 index 0000000..33b3808 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/config/WebMvcConfiguration.kt @@ -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); + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/context/BaseContext.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/context/BaseContext.kt new file mode 100644 index 0000000..ce5398d --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/context/BaseContext.kt @@ -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() + private val username = ThreadLocal() + private val loginVo = ThreadLocal() + + // 用户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() + } + } + +} \ No newline at end of file diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/BunnyException.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/BunnyException.kt new file mode 100644 index 0000000..ce9c616 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/BunnyException.kt @@ -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 + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/GlobalExceptionHandler.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/GlobalExceptionHandler.kt new file mode 100644 index 0000000..b3d65d5 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/GlobalExceptionHandler.kt @@ -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 { + 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 { + logger.error("GlobalExceptionHandler===>运行时异常信息:{}", exception.message) + exception.printStackTrace() + return Result.error(null, 500, "出错了啦") + } + + // 捕获系统异常 + @ExceptionHandler(Exception::class) + @ResponseBody + fun error(exception: Exception): Result { + logger.error("GlobalExceptionHandler===>系统异常信息:{}", exception.message) + + return Result.error(null, 500, "系统异常") + } + + // 特定异常处理 + @ExceptionHandler(ArithmeticException::class) + @ResponseBody + fun error(exception: ArithmeticException): Result { + 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 { + logger.error("GlobalExceptionHandler===>spring security异常:{}", exception.message) + + return Result.error(ResultCodeEnum.SERVICE_ERROR) + } + + // 处理SQL异常 + @ExceptionHandler(SQLIntegrityConstraintViolationException::class) + @ResponseBody + fun exceptionHandler(exception: SQLIntegrityConstraintViolationException): Result { + 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) + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/interceptor/UserTokenInterceptor.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/interceptor/UserTokenInterceptor.kt new file mode 100644 index 0000000..878e910 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/interceptor/UserTokenInterceptor.kt @@ -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? = 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() + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/EmptyUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/EmptyUtil.kt new file mode 100644 index 0000000..f196a64 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/EmptyUtil.kt @@ -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!!) + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/FileUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/FileUtil.kt new file mode 100644 index 0000000..8c3fff7 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/FileUtil.kt @@ -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" + } + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/IpUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/IpUtil.kt new file mode 100644 index 0000000..21c797c --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/IpUtil.kt @@ -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 "" + } ?: "" + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/JwtHelper.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/JwtHelper.kt new file mode 100644 index 0000000..76166d1 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/JwtHelper.kt @@ -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 { + 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?, 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?, 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?, 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?, 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?, 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?, 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?, 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?, 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? { + 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? { + 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 + } + } +} \ No newline at end of file diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseHandlerUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseHandlerUtil.kt new file mode 100644 index 0000000..5103749 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseHandlerUtil.kt @@ -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 + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseUtil.kt new file mode 100644 index 0000000..3d13b59 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/ResponseUtil.kt @@ -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?) { + 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() + } + } + } +} \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..af73d14 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + cn.bunny + BunnyBBS-server-admin + 0.0.1-SNAPSHOT + + + common + 1.0.0 + pom + + common + https://maven.apache.org + + common-generator + common-service + + + + UTF-8 + official + 17 + 17 + 2.0.20 + + + + + + cn.bunny + dao + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-web + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + io.jsonwebtoken + jjwt + + + + cn.hutool + hutool-all + + + + mysql + mysql-connector-java + + + + com.zaxxer + HikariCP + + + diff --git a/dao/pom.xml b/dao/pom.xml new file mode 100644 index 0000000..081116a --- /dev/null +++ b/dao/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + cn.bunny + BunnyBBS-server-admin + 0.0.1-SNAPSHOT + + + dao + + + UTF-8 + official + 17 + 17 + 2.0.20 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + + org.projectlombok + lombok + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + com.alibaba.fastjson2 + fastjson2 + + + + io.swagger + swagger-annotations + 1.6.14 + + + junit + junit + 3.8.1 + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 2.0.0 + test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + + + + + src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + compile + process-sources + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailTemplateDto.java b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailTemplateDto.java new file mode 100644 index 0000000..bfbe57d --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailTemplateDto.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.java b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.java new file mode 100644 index 0000000..37edf83 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.java b/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.java new file mode 100644 index 0000000..f1395a3 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/base/BaseEntity.java b/dao/src/main/kotlin/cn/bunny/dao/entity/base/BaseEntity.java new file mode 100644 index 0000000..5481e54 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/base/BaseEntity.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminPower.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminPower.java new file mode 100644 index 0000000..cff3ac1 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminPower.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRole.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRole.java new file mode 100644 index 0000000..694e605 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRole.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRolePower.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRolePower.java new file mode 100644 index 0000000..9231591 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminRolePower.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUser.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUser.java new file mode 100644 index 0000000..b46a9e8 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUser.java @@ -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; + +/** + *

+ * 管理员用户信息 + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUserRole.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUserRole.java new file mode 100644 index 0000000..0f96d61 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AdminUserRole.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AuthUserRole.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AuthUserRole.java new file mode 100644 index 0000000..0aab065 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/admin/AuthUserRole.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailTemplate.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailTemplate.java new file mode 100644 index 0000000..bd763bc --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailTemplate.java @@ -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; + +/** + *

+ * + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailUsers.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailUsers.java new file mode 100644 index 0000000..afe4f2b --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/email/EmailUsers.java @@ -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; + +/** + *

+ * 邮箱发送表 + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/log/SystemLog.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/log/SystemLog.java new file mode 100644 index 0000000..22713b3 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/log/SystemLog.java @@ -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; + +/** + *

+ * 系统日志表 + *

+ * + * @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; +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/system/user/User.java b/dao/src/main/kotlin/cn/bunny/dao/entity/system/user/User.java new file mode 100644 index 0000000..a6bb138 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/system/user/User.java @@ -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; + +/** + *

+ * 用户信息 + *

+ * + * @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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/ExceptionConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/ExceptionConstant.kt new file mode 100644 index 0000000..1e5ae96 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/ExceptionConstant.kt @@ -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 = "旧密码与新密码相同" + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/FileMessageConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/FileMessageConstant.kt new file mode 100644 index 0000000..8132594 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/FileMessageConstant.kt @@ -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 = "文件上传失败" + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/LocalDateTimeConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/LocalDateTimeConstant.kt new file mode 100644 index 0000000..fdb63c5 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/LocalDateTimeConstant.kt @@ -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" +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MailMessageConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MailMessageConstant.kt new file mode 100644 index 0000000..c8e8de3 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MailMessageConstant.kt @@ -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 = "邮箱配置为空" +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MinioConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MinioConstant.kt new file mode 100644 index 0000000..f4d3a7c --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MinioConstant.kt @@ -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 = 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) + } + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/RedisUserConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/RedisUserConstant.kt new file mode 100644 index 0000000..933b5f7 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/RedisUserConstant.kt @@ -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 + } + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/UserConstant.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/UserConstant.kt new file mode 100644 index 0000000..9a86195 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/UserConstant.kt @@ -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" + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSend.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSend.kt new file mode 100644 index 0000000..633e1bd --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSend.kt @@ -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? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSendInit.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSendInit.kt new file mode 100644 index 0000000..2bcf6b6 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSendInit.kt @@ -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 +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.java b/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.java new file mode 100644 index 0000000..f7137d1 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.java @@ -0,0 +1,8 @@ +package cn.bunny.dao.pojo.enums; + +/** + * 数据库操作类型 + */ +public enum OperationType { + UPDATE, INSERT +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.java b/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.java new file mode 100644 index 0000000..b1c8ca7 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.java b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.java new file mode 100644 index 0000000..edc8fd8 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.java @@ -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 implements Serializable { + // 当前页 + private Integer pageNo; + // 每页记录数 + private Integer pageSize; + // 总记录数 + private long total; + // 当前页数据集合 + private List list; +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/result/Result.java b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/Result.java new file mode 100644 index 0000000..d941e7c --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/Result.java @@ -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 { + // 状态码 + private Integer code; + // 返回消息 + private String message; + // 返回数据 + private T data; + + /** + * * 自定义返回体 + * + * @param data 返回体 + * @return Result + */ + protected static Result build(T data) { + Result result = new Result<>(); + result.setData(data); + return result; + } + + /** + * * 自定义返回体,使用ResultCodeEnum构建 + * + * @param body 返回体 + * @param codeEnum 返回状态码 + * @return Result + */ + public static Result build(T body, ResultCodeEnum codeEnum) { + Result result = build(body); + result.setCode(codeEnum.getCode()); + result.setMessage(codeEnum.getMessage()); + return result; + } + + /** + * * 自定义返回体 + * + * @param body 返回体 + * @param code 返回状态码 + * @param message 返回消息 + * @return Result + */ + public static Result build(T body, Integer code, String message) { + Result result = build(body); + result.setCode(code); + result.setMessage(message); + result.setData(null); + return result; + } + + /** + * * 操作成功 + * + * @return Result + */ + public static Result success() { + return success(null, ResultCodeEnum.SUCCESS); + } + + /** + * * 操作成功 + * + * @param data baseCategory1List + */ + public static Result success(T data) { + return build(data, ResultCodeEnum.SUCCESS); + } + + /** + * * 操作成功-状态码 + * + * @param codeEnum 状态码 + */ + public static Result success(ResultCodeEnum codeEnum) { + return success(null, codeEnum); + } + + /** + * * 操作成功-自定义返回数据和状态码 + * + * @param data 返回体 + * @param codeEnum 状态码 + */ + public static Result success(T data, ResultCodeEnum codeEnum) { + return build(data, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param message 错误信息 + */ + public static Result success(T data, String message) { + return build(data, 200, message); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param code 状态码 + * @param message 错误信息 + */ + public static Result success(T data, Integer code, String message) { + return build(data, code, message); + } + + /** + * * 操作失败 + */ + public static Result error() { + return Result.build(null); + } + + /** + * * 操作失败-自定义返回数据 + * + * @param data 返回体 + */ + public static Result error(T data) { + return build(data, ResultCodeEnum.FAIL); + } + + /** + * * 操作失败-状态码 + * + * @param codeEnum 状态码 + */ + public static Result error(ResultCodeEnum codeEnum) { + return build(null, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param codeEnum 状态码 + */ + public static Result error(T data, ResultCodeEnum codeEnum) { + return build(data, codeEnum); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param code 状态码 + * @param message 错误信息 + */ + public static Result error(T data, Integer code, String message) { + return build(data, code, message); + } + + /** + * * 操作失败-自定义返回数据和状态码 + * + * @param data 返回体 + * @param message 错误信息 + */ + public static Result error(T data, String message) { + return build(null, 500, message); + } +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/result/ResultCodeEnum.java b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/ResultCodeEnum.java new file mode 100644 index 0000000..b1abe66 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/ResultCodeEnum.java @@ -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; + } +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.java b/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.java new file mode 100644 index 0000000..8730fd5 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.java @@ -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; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.java b/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.java new file mode 100644 index 0000000..a5fa4ff --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.java @@ -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; +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/LoginVo.java b/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/LoginVo.java new file mode 100644 index 0000000..14b3361 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/LoginVo.java @@ -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 roleList; + private List powerList; +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/ValidateCodeVo.java b/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/ValidateCodeVo.java new file mode 100644 index 0000000..079e96d --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/system/login/ValidateCodeVo.java @@ -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; +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/system/user/UserInfoVo.java b/dao/src/main/kotlin/cn/bunny/dao/vo/system/user/UserInfoVo.java new file mode 100644 index 0000000..f2dda25 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/system/user/UserInfoVo.java @@ -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 roles; + private List powerCodeList; +} diff --git a/images/image-20240822091720866.png b/images/image-20240822091720866.png new file mode 100644 index 0000000..cefa1e5 Binary files /dev/null and b/images/image-20240822091720866.png differ diff --git a/images/image-20240822091810597.png b/images/image-20240822091810597.png new file mode 100644 index 0000000..2cfbd29 Binary files /dev/null and b/images/image-20240822091810597.png differ diff --git a/images/image-20240822092441020.png b/images/image-20240822092441020.png new file mode 100644 index 0000000..5b92d62 Binary files /dev/null and b/images/image-20240822092441020.png differ diff --git a/images/image-20240822092510151.png b/images/image-20240822092510151.png new file mode 100644 index 0000000..e822d45 Binary files /dev/null and b/images/image-20240822092510151.png differ diff --git a/images/image-20240822093552802.png b/images/image-20240822093552802.png new file mode 100644 index 0000000..0d6e6a9 Binary files /dev/null and b/images/image-20240822093552802.png differ diff --git a/images/image-20240822093803078.png b/images/image-20240822093803078.png new file mode 100644 index 0000000..cd507f3 Binary files /dev/null and b/images/image-20240822093803078.png differ diff --git a/images/image-20240822094000542.png b/images/image-20240822094000542.png new file mode 100644 index 0000000..ca37d8f Binary files /dev/null and b/images/image-20240822094000542.png differ diff --git a/images/image-20240822094021339.png b/images/image-20240822094021339.png new file mode 100644 index 0000000..ade461f Binary files /dev/null and b/images/image-20240822094021339.png differ diff --git a/module/module-mail/pom.xml b/module/module-mail/pom.xml new file mode 100644 index 0000000..0c0716e --- /dev/null +++ b/module/module-mail/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + + cn.bunny + module + 0.0.1-SNAPSHOT + + + module-mail + jar + + module-mail + https://maven.apache.org + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + src/main/kotlin + + diff --git a/module/module-mail/src/main/kotlin/cn/bunny/module/mail/template-propties b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/template-propties new file mode 100644 index 0000000..2ce3978 --- /dev/null +++ b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/template-propties @@ -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 # 必要设置!!! \ No newline at end of file diff --git a/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSendCheckUtil.java b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSendCheckUtil.java new file mode 100644 index 0000000..1001f58 --- /dev/null +++ b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSendCheckUtil.java @@ -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); + } +} diff --git a/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSenderUtil.java b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSenderUtil.java new file mode 100644 index 0000000..7a47028 --- /dev/null +++ b/module/module-mail/src/main/kotlin/cn/bunny/module/mail/utils/MailSenderUtil.java @@ -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); + } +} diff --git a/module/module-minio/pom.xml b/module/module-minio/pom.xml new file mode 100644 index 0000000..305368f --- /dev/null +++ b/module/module-minio/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + cn.bunny + module + 0.0.1-SNAPSHOT + + + module-minio + jar + + module-minio + https://maven.apache.org + + + UTF-8 + + + + + + io.minio + minio + + + + + src/main/kotlin + + diff --git a/module/module-minio/src/main/kotlin/cn/bunny/module/minio/properties/MinioProperties.java b/module/module-minio/src/main/kotlin/cn/bunny/module/minio/properties/MinioProperties.java new file mode 100644 index 0000000..f7e8f02 --- /dev/null +++ b/module/module-minio/src/main/kotlin/cn/bunny/module/minio/properties/MinioProperties.java @@ -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(); + } +} diff --git a/module/module-minio/src/main/kotlin/cn/bunny/module/minio/utils/MinioUtil.java b/module/module-minio/src/main/kotlin/cn/bunny/module/minio/utils/MinioUtil.java new file mode 100644 index 0000000..e3deb28 --- /dev/null +++ b/module/module-minio/src/main/kotlin/cn/bunny/module/minio/utils/MinioUtil.java @@ -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操作工具类 简化操作步骤 + * By:Bunny0212 + */ +@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); + } + } +} \ No newline at end of file diff --git a/module/module-rabbitMQ/pom.xml b/module/module-rabbitMQ/pom.xml new file mode 100644 index 0000000..579a0a7 --- /dev/null +++ b/module/module-rabbitMQ/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + cn.bunny + module + 0.0.1-SNAPSHOT + + + module-rabbitMQ + jar + + module-rabbitMQ + https://maven.apache.org + + + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.amqp + spring-rabbit-test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.16.0-rc1 + + + + + src/main/kotlin + + diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/config/RabbitMqConfig.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/config/RabbitMqConfig.java new file mode 100644 index 0000000..99abbc7 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/config/RabbitMqConfig.java @@ -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; + } +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DelayConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DelayConsumer.java new file mode 100644 index 0000000..ac1874d --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DelayConsumer.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DirectConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DirectConsumer.java new file mode 100644 index 0000000..6b3bc85 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/DirectConsumer.java @@ -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); + } + } +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/ErrorConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/ErrorConsumer.java new file mode 100644 index 0000000..583823e --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/ErrorConsumer.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/FanoutConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/FanoutConsumer.java new file mode 100644 index 0000000..0f65252 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/FanoutConsumer.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/LazyConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/LazyConsumer.java new file mode 100644 index 0000000..30b451f --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/LazyConsumer.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/TopicConsumer.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/TopicConsumer.java new file mode 100644 index 0000000..d6a8de0 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/consumer/TopicConsumer.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DelayListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DelayListener.java new file mode 100644 index 0000000..318cd63 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DelayListener.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DirectListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DirectListener.java new file mode 100644 index 0000000..aa83490 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/DirectListener.java @@ -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 + "】"); + } +} \ No newline at end of file diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/ErrorListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/ErrorListener.java new file mode 100644 index 0000000..22d2529 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/ErrorListener.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/FanoutListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/FanoutListener.java new file mode 100644 index 0000000..2d4d0ff --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/FanoutListener.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/LazyListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/LazyListener.java new file mode 100644 index 0000000..c682154 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/LazyListener.java @@ -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 { +} diff --git a/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/TopicListener.java b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/TopicListener.java new file mode 100644 index 0000000..9363906 --- /dev/null +++ b/module/module-rabbitMQ/src/main/kotlin/cn/bunny/module/rabbitMQ/listener/TopicListener.java @@ -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 { +} diff --git a/module/pom.xml b/module/pom.xml new file mode 100644 index 0000000..ce813cf --- /dev/null +++ b/module/pom.xml @@ -0,0 +1,114 @@ + + 4.0.0 + + cn.bunny + BunnyBBS-server-admin + 0.0.1-SNAPSHOT + + + module + pom + + module + https://maven.apache.org + + module-minio + module-rabbitMQ + spring-security + module-mail + + + + UTF-8 + official + 17 + 17 + 2.0.20 + + + + + cn.bunny + common-service + 1.0.0 + + + com.google.protobuf + protobuf-java + 4.27.2 + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 2.0.0 + test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + compile + process-sources + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + diff --git a/module/spring-security/pom.xml b/module/spring-security/pom.xml new file mode 100644 index 0000000..a659114 --- /dev/null +++ b/module/spring-security/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + cn.bunny + module + 0.0.1-SNAPSHOT + + + spring-security + jar + + spring-security + https://maven.apache.org + + + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-test + + + + + src/main/kotlin + + diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/config/WebSecurityConfig.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/config/WebSecurityConfig.kt new file mode 100644 index 0000000..019f4d9 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/config/WebSecurityConfig.kt @@ -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? = 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 -> obj.disable() } // 前端段分离不需要---禁用默认登录页 + .formLogin { obj: FormLoginConfigurer -> obj.disable() } // 前端段分离不需要---禁用退出页 + .logout { obj: LogoutConfigurer -> obj.disable() } // 前端段分离不需要---csrf攻击 + .csrf { obj: CsrfConfigurer -> obj.disable() } // 跨域访问权限,如果需要可以关闭后自己配置跨域访问 + .cors { obj: CorsConfigurer -> obj.disable() } // 前后端分离不需要---因为是无状态的 + .sessionManagement { obj: SessionManagementConfigurer -> obj.disable() } // 前后端分离不需要---记住我,e -> e.rememberMeParameter("rememberBunny").rememberMeCookieName("rememberBunny").key("BunnyKey") + .rememberMe { obj: RememberMeConfigurer -> obj.disable() } + .authorizeHttpRequests { authorize -> + // 有样式文件,不需要访问权限 + authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^\\S*[css|js]$")).permitAll() + // 上面都不是需要鉴权访问 + authorize.anyRequest().access(customAuthorizationManager) + } + .exceptionHandling { exception: ExceptionHandlingConfigurer -> + // 请求未授权接口 + 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) } + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomPasswordEncoder.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomPasswordEncoder.kt new file mode 100644 index 0000000..541b9dd --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomPasswordEncoder.kt @@ -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?> { + 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?) { + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomUser.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomUser.kt new file mode 100644 index 0000000..76374f4 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/custom/CustomUser.kt @@ -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?) : + org.springframework.security.core.userdetails.User( + user.email, user.password, authorities + ) \ No newline at end of file diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenAuthenticationFilter.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenAuthenticationFilter.kt new file mode 100644 index 0000000..6c502d5 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenAuthenticationFilter.kt @@ -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) : 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(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 = 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 + 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()) + } + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenLoginFilterService.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenLoginFilterService.kt new file mode 100644 index 0000000..986ed80 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/filter/TokenLoginFilterService.kt @@ -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的登录只能是表单形式 并且用户名密码需要时username、password,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数 + * 再去设置到 UsernamePasswordAuthenticationToken 中 来改变请求传参方式、参数名等 或者也可以在登录的时候加入其他参数等等 + */ +class TokenLoginFilterService( + authenticationConfiguration: AuthenticationConfiguration, + redisTemplate: RedisTemplate, + customUserDetailsService: CustomUserDetailsService +) : UsernamePasswordAuthenticationFilter() { + private val redisTemplate: RedisTemplate + 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)) + } + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAccessDeniedHandler.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAccessDeniedHandler.kt new file mode 100644 index 0000000..cecd7a6 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAccessDeniedHandler.kt @@ -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(ResultCodeEnum.FAIL_NO_ACCESS_DENIED) + + val json = JSON.toJSON(result) + + // 返回响应 + response.contentType = "application/json;charset=UTF-8" + response.writer.println(json) + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationEntryPoint.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationEntryPoint.kt new file mode 100644 index 0000000..e2e0d3b --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationEntryPoint.kt @@ -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 + + 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) + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationFailureHandler.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationFailureHandler.kt new file mode 100644 index 0000000..e8a0a7f --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationFailureHandler.kt @@ -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) + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationSuccessHandler.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationSuccessHandler.kt new file mode 100644 index 0000000..d320b62 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/handelr/SecurityAuthenticationSuccessHandler.kt @@ -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)) + } +} diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomAuthorizationManagerService.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomAuthorizationManagerService.kt new file mode 100644 index 0000000..75b4ae7 --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomAuthorizationManagerService.kt @@ -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 diff --git a/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomUserDetailsService.kt b/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomUserDetailsService.kt new file mode 100644 index 0000000..63d644e --- /dev/null +++ b/module/spring-security/src/main/kotlin/cn/bunny/security/service/CustomUserDetailsService.kt @@ -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 +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0da91da --- /dev/null +++ b/pom.xml @@ -0,0 +1,199 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + cn.bunny + BunnyBBS-server-admin + 0.0.1-SNAPSHOT + pom + bunny-template + bunny-template + + + services + common + dao + + + + 17 + 17 + 17 + 3.8.1 + 3.5.6 + 8.0.30 + 4.5.0 + 2.0.47 + 8.5.9 + 1.18.32 + 0.9.1 + 3.3.3 + 2.10.1 + 1.9.21 + 6.1.0 + 2.2 + 3.1 + 5.1.0 + 4.3.1 + 2.0.20 + + + + + + junit + junit + ${junit.version} + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + org.apache.velocity.tools + velocity-tools-generic + ${velocity-tools.version} + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + mysql + mysql-connector-java + ${mysql.version} + + + + com.zaxxer + HikariCP + ${HikariCP.version} + + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic.datasource.version} + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + + io.minio + minio + ${minio.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + + cn.hutool + hutool-all + 5.8.27 + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + org.aspectj + aspectjrt + ${aspectj} + + + + org.aspectj + aspectjweaver + ${aspectj} + + + joda-time + joda-time + ${jodatime.version} + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.12.3 + + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + ${maven.compiler.target} + + + + + diff --git a/services/Dockerfile b/services/Dockerfile new file mode 100644 index 0000000..4b3e1e6 --- /dev/null +++ b/services/Dockerfile @@ -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 \ No newline at end of file diff --git a/services/pom.xml b/services/pom.xml new file mode 100644 index 0000000..78529e9 --- /dev/null +++ b/services/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + cn.bunny + services + 0.0.1-SNAPSHOT + services + services + + + + + + + + + + + + + + + UTF-8 + official + 17 + 17 + 2.0.20 + + + + cn.bunny + common-service + 1.0.0 + + + + cn.bunny + module-rabbitMQ + 0.0.1-SNAPSHOT + + + + cn.bunny + spring-security + 0.0.1-SNAPSHOT + + + + cn.bunny + module-mail + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-test + + + + org.aspectj + aspectjrt + + + org.aspectj + aspectjweaver + + + + org.springframework.boot + spring-boot-starter-websocket + + + + com.baomidou + dynamic-datasource-spring-boot3-starter + 4.3.1 + + + + + ${project.basedir}/src/main/kotlin + src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + process-sources + + compile + + + + src/main/kotlin + target/generated-sources/annotations + + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + 17 + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + + + diff --git a/services/src/main/kotlin/cn/bunny/services/ServicesApplication.kt b/services/src/main/kotlin/cn/bunny/services/ServicesApplication.kt new file mode 100644 index 0000000..995a34d --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/ServicesApplication.kt @@ -0,0 +1,28 @@ +package cn.bunny.services + +import lombok.extern.slf4j.Slf4j +import org.mybatis.spring.annotation.MapperScan +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.cache.annotation.EnableCaching +import org.springframework.context.annotation.ComponentScan +import org.springframework.scheduling.annotation.EnableScheduling +import org.springframework.transaction.annotation.EnableTransactionManagement + +@MapperScan("cn.bunny.services.mapper") +@ComponentScan("cn.bunny") +@EnableScheduling +@EnableCaching +@EnableTransactionManagement +@Slf4j +@SpringBootApplication +class ServicesApplication { + companion object { + @JvmStatic + fun main(args: Array) { + runApplication(*args) + } + } +} + + diff --git a/services/src/main/kotlin/cn/bunny/services/aop/annotation/SkipLog.java b/services/src/main/kotlin/cn/bunny/services/aop/annotation/SkipLog.java new file mode 100644 index 0000000..382c68c --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/aop/annotation/SkipLog.java @@ -0,0 +1,11 @@ +package cn.bunny.services.aop.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SkipLog { +} diff --git a/services/src/main/kotlin/cn/bunny/services/aop/aspect/AutoFillAspect.java b/services/src/main/kotlin/cn/bunny/services/aop/aspect/AutoFillAspect.java new file mode 100644 index 0000000..457d306 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/aop/aspect/AutoFillAspect.java @@ -0,0 +1,27 @@ +package cn.bunny.services.aop.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; + +@Aspect +@Component +@Slf4j +public class AutoFillAspect { + @Pointcut("execution(* cn.bunny.services.service.impl..*(..))") + public void autoFillPointcut() { + } + + /** + * 之前操作 + * + * @param joinPoint 参数 + */ + @Before("autoFillPointcut()") + public void autoFill(JoinPoint joinPoint) { + log.info("开始进行自动填充"); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/IndexController.java b/services/src/main/kotlin/cn/bunny/services/controller/IndexController.java new file mode 100644 index 0000000..3bd19ae --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/IndexController.java @@ -0,0 +1,18 @@ +package cn.bunny.services.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "访问首页内容") +@RestController +@RequestMapping("/") +public class IndexController { + @Operation(summary = "访问首页", description = "访问首页") + @GetMapping("") + public String index() { + return "欢迎访问 Bunny Java Template,欢迎去Gitee:https://gitee.com/BunnyBoss/java_single.git"; + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/LoginController.java b/services/src/main/kotlin/cn/bunny/services/controller/LoginController.java new file mode 100644 index 0000000..041981d --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/LoginController.java @@ -0,0 +1,36 @@ +package cn.bunny.services.controller; + + +import cn.bunny.dao.dto.user.LoginDto; +import cn.bunny.dao.pojo.result.Result; +import cn.bunny.dao.vo.system.login.LoginVo; +import cn.bunny.services.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "登录相关接口") +@RestController +@RequestMapping("/admin") +public class LoginController { + @Autowired + private UserService userService; + + @Operation(summary = "登录接口", description = "后台用户登录接口") + @PostMapping("login") + public Result login(@RequestBody LoginDto loginDto) { + LoginVo vo = userService.login(loginDto); + return Result.success(vo); + } + + @Operation(summary = "发送邮箱验证码", description = "发送邮箱验证码") + @PostMapping("noAuth/sendEmail") + public Result sendEmail(String email) { + userService.sendEmail(email); + return Result.success(); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/WebController.java b/services/src/main/kotlin/cn/bunny/services/controller/WebController.java new file mode 100644 index 0000000..67d1742 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/WebController.java @@ -0,0 +1,31 @@ +package cn.bunny.services.controller; + +import cn.bunny.services.service.LoginService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "web相关接口") +@RestController +@RequestMapping("/api") +public class WebController { + @Autowired + private LoginService loginService; + + @Operation(summary = "生成验证码", description = "生成验证码") + @GetMapping("checkCode") + public ResponseEntity checkCode() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + + byte[] image = loginService.checkCode(); + return new ResponseEntity(image, headers, HttpStatus.OK); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/AdminPowerMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/AdminPowerMapper.java new file mode 100644 index 0000000..622ede4 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/AdminPowerMapper.java @@ -0,0 +1,34 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.system.admin.AdminPower; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-18 + */ +@Mapper +public interface AdminPowerMapper extends BaseMapper { + /** + * 查询用户权限信息 + * + * @param roleIdList 角色id 列表 + * @return 用户对应的权限 + */ + AdminPower[] selectByPowerWithRoleIdList(List roleIdList); + + /** + * 查询用户权限 + * + * @param userId 用户id + * @return 用户权限列表 + */ + List queryByUserIdWithPower(Long userId); +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/AdminRoleMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/AdminRoleMapper.java new file mode 100644 index 0000000..6ec3ce0 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/AdminRoleMapper.java @@ -0,0 +1,26 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.system.admin.AdminRole; +import cn.bunny.dao.entity.system.admin.AuthUserRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-18 + */ +@Mapper +public interface AdminRoleMapper extends BaseMapper { + + /** + * 查询用户所有的角色信息 + * + * @param userId 用户id + * @return 用户对应的角色 + */ + AuthUserRole[] selectByRoleWithUserId(Long userId); +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/EmailUsersMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/EmailUsersMapper.java new file mode 100644 index 0000000..8adb449 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/EmailUsersMapper.java @@ -0,0 +1,24 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.system.email.EmailUsers; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-14 + */ +@Mapper +public interface EmailUsersMapper extends BaseMapper { + + /** + * 彻底删除邮箱用户 + * + * @param id 用户ID + */ + void thoroughDeleteById(Long id); +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/SystemLogMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/SystemLogMapper.java new file mode 100644 index 0000000..ae09941 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/SystemLogMapper.java @@ -0,0 +1,18 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.system.log.SystemLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 系统日志表 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-31 + */ +@Mapper +public interface SystemLogMapper extends BaseMapper { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/UserMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/UserMapper.java new file mode 100644 index 0000000..cc978ae --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/UserMapper.java @@ -0,0 +1,25 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.system.user.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 用户信息 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-18 + */ +@Mapper +public interface UserMapper extends BaseMapper { + /** + * 前台用户登录接口 + * + * @param username 邮箱/昵称 + * @param password 吗,Image + * @return 登录参数 + */ + User login(String username, String password); +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/CustomAuthorizationManagerServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/security/CustomAuthorizationManagerServiceImpl.java new file mode 100644 index 0000000..fb90549 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/CustomAuthorizationManagerServiceImpl.java @@ -0,0 +1,72 @@ +package cn.bunny.services.security; + +import cn.bunny.common.service.utils.JwtHelper; +import cn.bunny.dao.entity.system.admin.AdminPower; +import cn.bunny.security.service.CustomAuthorizationManagerService; +import cn.bunny.services.mapper.AdminPowerMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.function.Supplier; + + +/** + * 自定义权限判断 + * 判断用户有哪些权限 + */ +@Component +@Slf4j +public class CustomAuthorizationManagerServiceImpl implements CustomAuthorizationManagerService { + @Autowired + private AdminPowerMapper adminPowerMapper; + + @Override + public void verify(Supplier authentication, RequestAuthorizationContext requestAuthorizationContext) { + CustomAuthorizationManagerService.super.verify(authentication, requestAuthorizationContext); + } + + @Override + public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { + // 用户的token和用户id、请求Url + HttpServletRequest request = context.getRequest(); + String token = request.getHeader("token"); + Long userId = JwtHelper.getUserId(token);// 用户id + String requestURI = request.getRequestURI();// 请求地址 + String method = request.getMethod();// 请求方式 + List roleCodeList = authentication.get().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList();// 角色代码列表 + if (token == null) { + throw new AccessDeniedException(""); + } + + return new AuthorizationDecision(hasRoleList(requestURI, method, userId)); + } + + /** + * 查询用户所属的角色信息 + * + * @param requestURI 请求url地址 + * @param method 请求方式 + * @param userId 用户id + */ + private Boolean hasRoleList(String requestURI, String method, Long userId) { + // 查询用户权限 + List powerList = adminPowerMapper.queryByUserIdWithPower(userId); + // 如果查询到当前地址符合这个地址 + for (AdminPower adminPower : powerList) { + String description = adminPower.getDescription(); + if (description.equals(requestURI) || requestURI.matches(description)) { + return true; + } + } + + return false; + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/CustomUserDetailsService.java b/services/src/main/kotlin/cn/bunny/services/security/CustomUserDetailsService.java new file mode 100644 index 0000000..4ed55a1 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/CustomUserDetailsService.java @@ -0,0 +1,56 @@ +package cn.bunny.services.security; + +import cn.bunny.dao.dto.user.LoginDto; +import cn.bunny.dao.entity.system.admin.AdminRole; +import cn.bunny.dao.entity.system.user.User; +import cn.bunny.dao.vo.system.login.LoginVo; +import cn.bunny.security.custom.CustomUser; +import cn.bunny.services.mapper.AdminRoleMapper; +import cn.bunny.services.mapper.UserMapper; +import cn.bunny.services.service.UserService; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class CustomUserDetailsService implements cn.bunny.security.service.CustomUserDetailsService { + @Autowired + private UserMapper userMapper; + @Autowired + private UserService userService; + @Autowired + private AdminRoleMapper adminRoleMapper; + + @NotNull + @Override + public UserDetails loadUserByUsername(@NotNull String username) throws UsernameNotFoundException { + // 根据邮箱查询用户名 + User user = userMapper.selectOne(Wrappers.lambdaQuery().eq(User::getEmail, username)); + List sysRoleList = adminRoleMapper.selectList(null); + // 都为空抛出异常,用户不存在 + if (user == null) { + throw new UsernameNotFoundException(""); + } + + // 查询所有的角色 + List roleAuthoritieList = sysRoleList.stream().map(AdminRole::getRoleCode).toList(); + return new CustomUser(user, AuthorityUtils.createAuthorityList(roleAuthoritieList)); + } + + /** + * 前台用户登录接口 + * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + @Override + public LoginVo login(LoginDto loginDto) { + return userService.login(loginDto); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/LoginService.java b/services/src/main/kotlin/cn/bunny/services/service/LoginService.java new file mode 100644 index 0000000..9f05d6b --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/LoginService.java @@ -0,0 +1,10 @@ +package cn.bunny.services.service; + +public interface LoginService { + /** + * * 生成验证码 + * + * @return 验证码图片数组 + */ + byte[] checkCode(); +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/UserService.java b/services/src/main/kotlin/cn/bunny/services/service/UserService.java new file mode 100644 index 0000000..b527bd0 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/UserService.java @@ -0,0 +1,31 @@ +package cn.bunny.services.service; + +import cn.bunny.dao.dto.user.LoginDto; +import cn.bunny.dao.entity.system.user.User; +import cn.bunny.dao.vo.system.login.LoginVo; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 用户信息 服务类 + *

+ * + * @author Bunny + * @since 2024-05-18 + */ +public interface UserService extends IService { + /** + * 前台用户登录接口 + * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + LoginVo login(LoginDto loginDto); + + /** + * 发送邮箱验证码 + * + * @param email 邮箱 + */ + void sendEmail(String email); +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/impl/LoginServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..27a2c57 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/impl/LoginServiceImpl.java @@ -0,0 +1,21 @@ +package cn.bunny.services.service.impl; + +import cn.bunny.services.service.LoginService; +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.CircleCaptcha; +import org.springframework.stereotype.Service; + +@Service +public class LoginServiceImpl implements LoginService { + /** + * * 生成验证码 + * + * @return 验证码图片数组 + */ + @Override + public byte[] checkCode() { + // 生成验证码 + CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2); + return captcha.getImageBytes(); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.kt b/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.kt new file mode 100644 index 0000000..5190c9b --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.kt @@ -0,0 +1,124 @@ +package cn.bunny.services.service.impl + +import cn.bunny.common.service.exception.BunnyException +import cn.bunny.common.service.utils.EmptyUtil.isEmpty +import cn.bunny.common.service.utils.JwtHelper.createToken +import cn.bunny.dao.dto.user.LoginDto +import cn.bunny.dao.entity.system.admin.AdminPower +import cn.bunny.dao.entity.system.admin.AuthUserRole +import cn.bunny.dao.entity.system.email.EmailUsers +import cn.bunny.dao.entity.system.user.User +import cn.bunny.dao.pojo.constant.ExceptionConstant +import cn.bunny.dao.pojo.constant.MailMessageConstant +import cn.bunny.dao.pojo.constant.RedisUserConstant +import cn.bunny.dao.pojo.email.EmailSend +import cn.bunny.dao.pojo.email.EmailSendInit +import cn.bunny.dao.vo.system.login.LoginVo +import cn.bunny.module.mail.utils.MailSenderUtil +import cn.bunny.services.mapper.AdminPowerMapper +import cn.bunny.services.mapper.AdminRoleMapper +import cn.bunny.services.mapper.EmailUsersMapper +import cn.bunny.services.mapper.UserMapper +import cn.bunny.services.service.UserService +import cn.hutool.captcha.CaptchaUtil +import com.baomidou.mybatisplus.core.toolkit.Wrappers +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl +import org.springframework.beans.BeanUtils +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.stereotype.Service +import org.springframework.util.DigestUtils +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * 用户信息 服务实现类 + * + * @author Bunny + * @since 2024-05-18 + */ +@Service +class UserServiceImpl : ServiceImpl(), UserService { + @Autowired + private val roleMapper: AdminRoleMapper? = null + + @Autowired + private val powerMapper: AdminPowerMapper? = null + + @Autowired + private val emailUsersMapper: EmailUsersMapper? = null + + @Autowired + private val redisTemplate: RedisTemplate? = null + + /** + * 前台用户登录接口 + * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + override fun login(loginDto: LoginDto): LoginVo { + // 判断用户和密码是否为空 + val username = loginDto.username + isEmpty(username, ExceptionConstant.USERNAME_IS_EMPTY_EXCEPTION) + // 密码不能为空 + isEmpty(loginDto.password, ExceptionConstant.PASSWORD_NOT_EMPTY_EXCEPTION) + val password = DigestUtils.md5DigestAsHex(loginDto.password.toByteArray()) + // 查询数据库,用户对应的角色、权限 + val user = + baseMapper!!.login(username, password) ?: throw BunnyException(ExceptionConstant.USER_NOT_FOUND_EXCEPTION) + + // 查询用户所有的角色信息 + val roleList = roleMapper!!.selectByRoleWithUserId(user.id) + val roleCodeList = Arrays.stream(roleList).map { obj: AuthUserRole -> obj.roleCode } + .toList() + val roleIdList = Arrays.stream(roleList).map { obj: AuthUserRole -> obj.roleId }.toList() + // 查询用户权限信息 + val adminPowerList = powerMapper!!.selectByPowerWithRoleIdList(roleIdList) + val powerCodeList = Arrays.stream(adminPowerList).map { obj: AdminPower -> obj.powerCode } + .toList() + + // 设置返回类型 + val loginVo = LoginVo() + BeanUtils.copyProperties(user, loginVo) + val token = createToken(loginVo.id, loginVo.email, 7) + loginVo.token = token + loginVo.roleList = roleCodeList + loginVo.powerList = powerCodeList + + return loginVo + } + + /** + * 发送邮箱验证码 + * + * @param email 邮箱 + */ + override fun sendEmail(email: String) { + // 从数据库中获取发送邮箱参数 + val emailUsers = emailUsersMapper!!.selectOne( + Wrappers.lambdaQuery().eq(EmailUsers::getIsDefault, 1) + ) + val emailSendInit = EmailSendInit() + // 判断发送邮箱邮件是否为空 + isEmpty(emailUsers, MailMessageConstant.EMAIL_CONFIG_NOT_FOUND) + BeanUtils.copyProperties(emailUsers, emailSendInit) + emailSendInit.username = emailUsers.email + // 生成验证码 + val mailSenderUtil = MailSenderUtil(emailSendInit) + val captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2) + // 生成验证码和可以 + val code = captcha.code + // 发送验证码 + val emailSend = EmailSend() + emailSend.subject = "邮箱验证码" + emailSend.message = code + emailSend.sendTo = email + emailSend.isRichText = false + mailSenderUtil.sendSimpleEmail(emailSend) + + // 将验证码保存到Redis中,并设置15分钟过期 + redisTemplate!!.opsForValue() + .set("${RedisUserConstant.ADMIN_EMAIL_CODE_PREFIX}$email", code, 15, TimeUnit.MINUTES) + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/task/TemplateTask.java b/services/src/main/kotlin/cn/bunny/services/task/TemplateTask.java new file mode 100644 index 0000000..01739e6 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/task/TemplateTask.java @@ -0,0 +1,14 @@ +package cn.bunny.services.task; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class TemplateTask { + @Scheduled(cron = "0/1 5 * * * ?") + public void templateTask() { + log.info("定时任务执行..."); + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/ws/WebSocketServer.java b/services/src/main/kotlin/cn/bunny/services/ws/WebSocketServer.java new file mode 100644 index 0000000..c0105c3 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/ws/WebSocketServer.java @@ -0,0 +1,71 @@ +package cn.bunny.services.ws; + +import jakarta.websocket.OnClose; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * WebSocket服务 + */ +@Component +@ServerEndpoint("/ws/{sid}") +public class WebSocketServer { + + // 存放会话对象 + private static final Map sessionMap = new HashMap(); + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session, @PathParam("sid") String sid) { + System.out.println("客户端:" + sid + "建立连接"); + sessionMap.put(sid, session); + } + + /** + * 收到客户端消息后调用的方法 + * + * @param message 客户端发送过来的消息 + */ + @OnMessage + public void onMessage(String message, @PathParam("sid") String sid) { + System.out.println("收到来自客户端:" + sid + "的信息:" + message); + } + + /** + * 连接关闭调用的方法 + * + * @param sid 请求id + */ + @OnClose + public void onClose(@PathParam("sid") String sid) { + System.out.println("连接断开:" + sid); + sessionMap.remove(sid); + } + + /** + * 群发 + * + * @param message 消息 + */ + public void sendToAllClient(String message) { + Collection sessions = sessionMap.values(); + for (Session session : sessions) { + try { + // 服务器向客户端发送消息 + session.getBasicRemote().sendText(message); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/services/src/main/resources/application-dev.yml b/services/src/main/resources/application-dev.yml new file mode 100644 index 0000000..7201332 --- /dev/null +++ b/services/src/main/resources/application-dev.yml @@ -0,0 +1,25 @@ +bunny: + datasource: + host: 192.168.3.98 + port: 3304 + sqlData: bunny_docs + username: root + password: "02120212" + datasource2: + host: 106.15.251.123 + port: 3305 + sqlData: bunny_docs_i18n + username: root + password: "02120212" + + redis: + host: 192.168.3.98 + port: 6379 + database: 0 + password: "123456" + + minio: + endpointUrl: "http://192.168.3.98:9000" + accessKey: bunny + secretKey: "02120212" + bucket-name: bunny-bbs diff --git a/services/src/main/resources/application-prod.yml b/services/src/main/resources/application-prod.yml new file mode 100644 index 0000000..522e7b7 --- /dev/null +++ b/services/src/main/resources/application-prod.yml @@ -0,0 +1,25 @@ +bunny: + datasource: + host: 106.15.251.123 + port: 3305 + sqlData: bunny_docs + username: root + password: "02120212" + datasource2: + host: 106.15.251.123 + port: 3305 + sqlData: bunny_docs_i18n + username: root + password: "02120212" + + redis: + host: 47.120.65.66 + port: 6379 + database: 0 + password: "02120212" + + minio: + endpointUrl: "http://116.196.101.14:9000" + accessKey: bunny + secretKey: "02120212" + bucket-name: bunny-bbs diff --git a/services/src/main/resources/application.yml b/services/src/main/resources/application.yml new file mode 100644 index 0000000..b87a9ef --- /dev/null +++ b/services/src/main/resources/application.yml @@ -0,0 +1,79 @@ +server: + port: 8801 +spring: + profiles: + active: dev + application: + name: service-admin + main: + lazy-initialization: true + jmx: + enabled: false + datasource: + 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} + i18n: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://${bunny.datasource2.host}:${bunny.datasource2.port}/${bunny.datasource2.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: ${bunny.datasource2.username} + password: ${bunny.datasource2.password} + aop: + enabled: true + + data: + redis: + host: ${bunny.redis.host} + port: ${bunny.redis.port} + database: ${bunny.redis.database} + password: ${bunny.redis.password} + lettuce: + pool: + max-active: 20 #最大连接数 + max-wait: -1 #最大阻塞等待时间(负数表示没限制) + max-idle: 5 #最大空闲 + min-idle: 0 #最小空闲 + + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + servlet: + multipart: + max-file-size: 5MB + +mybatis-plus: + mapper-locations: classpath:mapper/*.xml + global-config: + db-config: + logic-delete-field: isDeleted + configuration: + map-underscore-to-camel-case: true +# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志 + +logging: + level: + cn.bunny.service.controller: info + cn.bunny.service.service: info + cn.bunny.service.mapper: info + root: info + + pattern: + dateformat: HH:mm:ss:SSS + file: + path: "logs/${spring.application.name}" + +bunny: + minio: + endpointUrl: ${bunny.minio.endpointUrl} + accessKey: ${bunny.minio.accessKey} + secretKey: ${bunny.minio.secretKey} + bucket-name: ${bunny.minio.bucket-name} \ No newline at end of file diff --git a/services/src/main/resources/banner.txt b/services/src/main/resources/banner.txt new file mode 100644 index 0000000..c34ce34 --- /dev/null +++ b/services/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +__________ ____ __. __ .__ .__ +\______ \__ __ ____ ____ ___.__. | |/ _|_____/ |_| | |__| ____ + | | _/ | \/ \ / < | | | < / _ \ __\ | | |/ \ + | | \ | / | \ | \___ | | | ( <_> ) | | |_| | | \ + |______ /____/|___| /___| / ____| |____|__ \____/|__| |____/__|___| / + \/ \/ \/\/ \/ \/ \ No newline at end of file diff --git a/services/src/main/resources/ipdb/ip2region.xdb b/services/src/main/resources/ipdb/ip2region.xdb new file mode 100644 index 0000000..7052c05 Binary files /dev/null and b/services/src/main/resources/ipdb/ip2region.xdb differ diff --git a/services/src/main/resources/logback.xml b/services/src/main/resources/logback.xml new file mode 100644 index 0000000..04ecbe3 --- /dev/null +++ b/services/src/main/resources/logback.xml @@ -0,0 +1,61 @@ + + + + + + + %cyan([%thread]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line) - %blue(%msg%n) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/src/main/resources/mapper/AdminPowerMapper.xml b/services/src/main/resources/mapper/AdminPowerMapper.xml new file mode 100644 index 0000000..4ec135d --- /dev/null +++ b/services/src/main/resources/mapper/AdminPowerMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + id, power_name, power_code, description, create_time, update_time, update_user, is_delete + + + + + + + + diff --git a/services/src/main/resources/mapper/AdminRoleMapper.xml b/services/src/main/resources/mapper/AdminRoleMapper.xml new file mode 100644 index 0000000..40f753b --- /dev/null +++ b/services/src/main/resources/mapper/AdminRoleMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + id, role_name, description, role_code, create_time, update_time, update_user, is_deleted + + + + + diff --git a/services/src/main/resources/mapper/EmailUsersMapper.xml b/services/src/main/resources/mapper/EmailUsersMapper.xml new file mode 100644 index 0000000..54cdd7f --- /dev/null +++ b/services/src/main/resources/mapper/EmailUsersMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + id, email, password, host, port, create_time, update_time, update_user, is_delete + + + + + delete + from email_users + where id = #{id} + + + diff --git a/services/src/main/resources/mapper/SystemLogMapper.xml b/services/src/main/resources/mapper/SystemLogMapper.xml new file mode 100644 index 0000000..6aec1b2 --- /dev/null +++ b/services/src/main/resources/mapper/SystemLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, class_path, method_name, args, result, error_stack, error_message, email, nickname, token, create_time, update_time, update_user, is_deleted + + + diff --git a/services/src/main/resources/mapper/UserMapper.xml b/services/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..7785acc --- /dev/null +++ b/services/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/src/main/resources/static/favicon.ico b/services/src/main/resources/static/favicon.ico new file mode 100644 index 0000000..385f8a6 Binary files /dev/null and b/services/src/main/resources/static/favicon.ico differ