commit 1c8238bbb711cb8a7e2fcc3e6c04a3e1a1609748 Author: bunny <1319900154@qq.com> Date: Thu Sep 26 09:38:54 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/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/AdminGenerator.kt b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/AdminGenerator.kt new file mode 100644 index 0000000..1db25d8 --- /dev/null +++ b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/AdminGenerator.kt @@ -0,0 +1,82 @@ +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 AdminGenerator { + companion object { + // 数据连接 + private const val SQL_HOST: String = + "jdbc:mysql://192.168.3.98:3304/auth_admin?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true" + + // 作者名称 + private const val AUTHOR: String = "Bunny" + + // 公共路径 + private const val OUTPUT_DIR: String = "D:\\MyFolder\\auth-admin\\auth-server\\services" + + // 实体类名称 + private const val ENTITY: String = "Bunny" + + @JvmStatic + fun main(args: Array) { + generation("sys_user") + } + + /** + * 根据表名生成相应结构代码 + * + * @param tableName 表名 + */ + fun generation(vararg tableName: String) { + FastAutoGenerator.create(SQL_HOST, "root", "02120212") + .globalConfig { builder -> + // 添加作者名称 + builder.author(AUTHOR) // 启用swagger + .enableSwagger() // 指定输出目录 + .outputDir("$OUTPUT_DIR/src/main/kotlin") + } + .packageConfig { builder -> + builder.entity(ENTITY) // 实体类包名 + .parent("cn.bunny.services") + .controller("controller") // 控制层包名 + .mapper("mapper") // mapper层包名 + .service("service") // service层包名 + .serviceImpl("service.impl") // service实现类包名 + // 自定义mapper.xml文件输出目录 + .pathInfo(Collections.singletonMap(OutputFile.xml, "$OUTPUT_DIR/src/main/resources/mapper")) + } + .strategyConfig { builder -> + // 设置要生成的表名 + builder.addInclude(*tableName) + .addTablePrefix("sys_")// 设置表前缀过滤 + .entityBuilder() + .enableLombok() + .enableChainModel() + .naming(NamingStrategy.underline_to_camel) // 数据表映射实体命名策略:默认下划线转驼峰underline_to_camel + .columnNaming(NamingStrategy.underline_to_camel) // 表字段映射实体属性命名规则:默认null,不指定按照naming执行 + .idType(IdType.AUTO) // 添加全局主键类型 + .formatFileName("%s") // 格式化实体名称,%s取消首字母I, + .mapperBuilder() + .mapperAnnotation(Mapper::class.java) // 开启mapper注解 + .enableBaseResultMap() // 启用xml文件中的BaseResultMap 生成 + .enableBaseColumnList() // 启用xml文件中的BaseColumnList + .formatMapperFileName("%sMapper") // 格式化Dao类名称 + .formatXmlFileName("%sMapper") // 格式化xml文件名称 + .serviceBuilder() + .formatServiceFileName("%sService") // 格式化 service 接口文件名称 + .formatServiceImplFileName("%sServiceImpl") // 格式化 service 接口文件名称 + .controllerBuilder() + .enableRestStyle() + } + .execute() + } + } +} diff --git a/common/common-generator/src/main/kotlin/cn/bunny/common/generator/I18nGenerator.kt b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/I18nGenerator.kt new file mode 100644 index 0000000..60d45b8 --- /dev/null +++ b/common/common-generator/src/main/kotlin/cn/bunny/common/generator/I18nGenerator.kt @@ -0,0 +1,26 @@ +package cn.bunny.common.generator + +/** + * 代码生成器 + */ +class I18nGenerator { + companion object { + // 数据连接 + private const val SQL_HOST: String = + "jdbc:mysql://106.15.251.123:3305/auth_i18n?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true" + + // 作者名称 + private const val AUTHOR: String = "Bunny" + + // 公共路径 + private const val OUTPUT_DIR: String = "D:\\MyFolder\\auth-admin\\auth-server\\services" + + // 实体类名称 + private const val ENTITY: String = "Bunny" + + @JvmStatic + fun main(args: Array) { + AdminGenerator.generation("i18n", "i18n_type") + } + } +} diff --git a/common/common-service/pom.xml b/common/common-service/pom.xml new file mode 100644 index 0000000..c95fd1b --- /dev/null +++ b/common/common-service/pom.xml @@ -0,0 +1,97 @@ + + + 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 + + + + io.minio + minio + + + org.springframework.boot + spring-boot-starter-mail + + + + org.lionsoul + ip2region + 2.6.5 + + + + + src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + 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/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..e4a985a --- /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.user.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..2bd5277 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/exception/GlobalExceptionHandler.kt @@ -0,0 +1,98 @@ +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 +import java.util.regex.Pattern + + +@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) + + // 错误消息 + val message = exception.message ?: "" + // 匹配到内容 + val patternString = "Request method '(\\w+)' is not supported" + val matcher = Pattern.compile(patternString).matcher(message) + + return when { + matcher.find() -> Result.error(null, 500, "请求方法错误,不是 " + matcher.group(1)) + else -> 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/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/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSendCheckUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSendCheckUtil.kt new file mode 100644 index 0000000..30a06aa --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSendCheckUtil.kt @@ -0,0 +1,26 @@ +package cn.bunny.common.service.utils.email + +import cn.bunny.common.service.utils.EmptyUtil.isEmpty +import cn.bunny.dao.pojo.constant.MailMessageConstant +import cn.bunny.dao.pojo.email.EmailSend + +class MailSendCheckUtil { + companion object { + /** + * 检测发送对象是否为空的对象 + * + * @param emailSend 邮件发送对象 + */ + @JvmStatic + fun check(emailSend: EmailSend) { + // 空发送对象 + isEmpty(emailSend, MailMessageConstant.EMPTY_SEND_OBJECT) + // 收件人不能为空 + isEmpty(emailSend.sendTo, MailMessageConstant.ADDRESS_NOT_NULL) + // 标题不能为空 + isEmpty(emailSend.subject, MailMessageConstant.TITLE_NOT_NULL) + // 发送消息不能为空 + isEmpty(emailSend.message, MailMessageConstant.SEND_MESSAGE_NOT_NULL) + } + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSenderUtil.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSenderUtil.kt new file mode 100644 index 0000000..06e1077 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/MailSenderUtil.kt @@ -0,0 +1,64 @@ +package cn.bunny.common.service.utils.email + +import cn.bunny.common.service.utils.email.MailSendCheckUtil.Companion.check +import cn.bunny.dao.pojo.email.EmailSend +import cn.bunny.dao.pojo.email.EmailSendInit +import jakarta.mail.MessagingException +import org.springframework.mail.javamail.JavaMailSenderImpl +import org.springframework.mail.javamail.MimeMessageHelper +import java.util.* + +class MailSenderUtil(emailSendInit: EmailSendInit) { + private val username: String? + private val javaMailSender: JavaMailSenderImpl + + /** + * 初始化构造函数进行当前类赋值 + */ + init { + val javaMailSender = JavaMailSenderImpl() + javaMailSender.host = emailSendInit.host + javaMailSender.port = emailSendInit.port!! + javaMailSender.username = emailSendInit.username + javaMailSender.password = emailSendInit.password + javaMailSender.protocol = "smtps" + javaMailSender.defaultEncoding = "UTF-8" + + this.username = emailSendInit.username + this.javaMailSender = javaMailSender + } + + /** + * 综合邮箱发送 + * + * @param emailSend 邮件消息 + */ + @Throws(MessagingException::class) + fun sendEmail(emailSend: EmailSend) { + check(emailSend) + + // 创建 MimeMessage 对象用于发送邮件富文本或者附件 + val message = javaMailSender.createMimeMessage() + // 创建 MimeMessageHelper + val helper = MimeMessageHelper(message, true) + + // 设置发送人 + helper.setFrom(username!!) + // 设置邮件接受者 + helper.setTo(emailSend.sendTo!!) + // 设置邮件主题 + helper.setSubject(emailSend.subject!!) + // 设置发送消息 为富文本 + helper.setText(emailSend.message!!, emailSend.isRichText!!) + // 设置抄送人 + helper.setCc(emailSend.ccParam!!.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) + // 邮件添加附件 + val files = emailSend.file + for (file in files!!) { + helper.addAttachment(Objects.requireNonNull(file.originalFilename), file) + } + + // 发送邮件 + javaMailSender.send(message) + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/template-propties b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/template-propties new file mode 100644 index 0000000..2ce3978 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/email/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/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioProperties.kt b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioProperties.kt new file mode 100644 index 0000000..603ee19 --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioProperties.kt @@ -0,0 +1,26 @@ +package cn.bunny.common.service.utils.minio + +import io.minio.MinioClient +import lombok.Data +import lombok.extern.slf4j.Slf4j +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +@ConfigurationProperties(prefix = "bunny.minio") +@ConditionalOnProperty(name = ["bunny.minio.bucket-name"]) // 当属性有值时这个配置才生效 +@Data +@Slf4j +open class MinioProperties { + val endpointUrl: String? = null + val accessKey: String? = null + val secretKey: String? = null + val bucketName: String? = null + + @Bean + open fun minioClient(): MinioClient { + return MinioClient.builder().endpoint(endpointUrl).credentials(accessKey, secretKey).build() + } +} diff --git a/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioUtil.java b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioUtil.java new file mode 100644 index 0000000..2ead1df --- /dev/null +++ b/common/common-service/src/main/kotlin/cn/bunny/common/service/utils/minio/MinioUtil.java @@ -0,0 +1,142 @@ +package cn.bunny.common.service.utils.minio; + +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 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/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..37baf9f --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + cn.bunny + kotlin-single + 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..59a58b9 --- /dev/null +++ b/dao/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + cn.bunny + kotlin-single + 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 + + + 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.kt b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailTemplateDto.kt new file mode 100644 index 0000000..fde361e --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailTemplateDto.kt @@ -0,0 +1,25 @@ +package cn.bunny.dao.dto.email + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.NoArgsConstructor + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "EmailTemplateDto", title = "邮箱模板请求内容", description = "邮箱模板请求内容") +class EmailTemplateDto { + @Schema(name = "templateName", title = "模板名称") + var templateName: @NotBlank(message = "模板名称不能为空") String? = null + + @Schema(name = "subject", title = "主题") + var subject: @NotBlank(message = "主题不能为空") String? = null + + @Schema(name = "body", title = "邮件内容") + var body: @NotBlank(message = "邮件内容不能为空") String? = null + + @Schema(name = "type", title = "邮件类型") + var type: String? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.kt b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.kt new file mode 100644 index 0000000..5cc1dd4 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/email/EmailUsersDto.kt @@ -0,0 +1,40 @@ +package cn.bunny.dao.dto.email + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor + +/** + * 添加邮箱用户 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "EmailUsersDto", title = "邮箱用户发送基础内容", description = "邮箱用户发送基础内容") +class EmailUsersDto { + @Schema(name = "id", title = "主键") + var id: @NotBlank(message = "id不能为空") Long? = null + + @Schema(name = "email", title = "邮箱") + var email: @NotBlank(message = "邮箱不能为空") String? = null + + @Schema(name = "password", title = "密码") + var password: @NotBlank(message = "密码不能为空") String? = null + + @Schema(name = "host", title = "SMTP服务器") + var host: String? = null + + @Schema(name = "port", title = "端口号") + var port: @NotNull(message = "端口号不能为空") Int? = null + + @Schema(name = "smtpAgreement", title = "邮箱协议") + var smtpAgreement: Int? = null + + @Schema(name = "isDefault", title = "是否为默认邮件") + var isDefault: Boolean? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.kt b/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.kt new file mode 100644 index 0000000..313582c --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/dto/user/LoginDto.kt @@ -0,0 +1,24 @@ +package cn.bunny.dao.dto.user + +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "LoginDto", title = "登录表单内容", description = "登录表单内容") +class LoginDto { + @Schema(name = "username", title = "用户名") + var username: @NotBlank(message = "用户名不能为空") String? = null + + @Schema(name = "password", title = "密码") + var password: @NotBlank(message = "密码不能为空") String? = null + + @Schema(name = "emailCode", title = "邮箱验证码") + var emailCode: @NotBlank(message = "邮箱验证码不能为空") String? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/BaseEntity.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/BaseEntity.kt new file mode 100644 index 0000000..d65f110 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/BaseEntity.kt @@ -0,0 +1,46 @@ +package cn.bunny.dao.entity + +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 io.swagger.v3.oas.annotations.media.Schema +import lombok.Data +import java.io.Serializable +import java.time.LocalDateTime + +@Data +@Schema(name = "BaseEntity", title = "基础实体类型", description = "基础实体类型") +open class BaseEntity : Serializable { + @Schema(name = "id", title = "唯一标识") + @TableId(value = "id", type = IdType.ASSIGN_ID) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var id: Long? = null + + @ApiModelProperty("创建时间") + @Schema(name = "createTime", title = "创建时间") + @TableField(fill = FieldFill.INSERT) + var createTime: LocalDateTime? = null + + @Schema(name = "updateTime", title = "更新时间") + @TableField(fill = FieldFill.INSERT_UPDATE) + var updateTime: LocalDateTime? = null + + @Schema(name = "createUser", title = "创建用户") + @TableField(fill = FieldFill.INSERT) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var createUser: Long? = null + + @Schema(name = "updateUser", title = "操作用户") + @TableField(fill = FieldFill.INSERT_UPDATE) + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var updateUser: Long? = null + + @Schema(name = "isDeleted", title = "是否被删除") + @TableLogic + var isDeleted: Boolean? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailTemplate.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailTemplate.kt new file mode 100644 index 0000000..ed75e66 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailTemplate.kt @@ -0,0 +1,36 @@ +package cn.bunny.dao.entity.email + +import cn.bunny.dao.entity.BaseEntity +import com.baomidou.mybatisplus.annotation.TableName +import io.swagger.v3.oas.annotations.media.Schema +import lombok.Getter +import lombok.Setter +import lombok.experimental.Accessors + +/** + * + * + * + * + * + * @author Bunny + * @since 2024-05-19 + */ +@Getter +@Setter +@Accessors(chain = true) +@TableName("email_template") +@Schema(name = "EmailTemplate", title = "邮件模板", description = "邮件模板") +class EmailTemplate : BaseEntity() { + @Schema(name = "templateName", title = "模板名称") + var templateName: String? = null + + @Schema(name = "subject", title = "主题") + var subject: String? = null + + @Schema(name = "body", title = "邮件内容") + var body: String? = null + + @Schema(name = "type", title = "邮件类型") + var type: String? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailUsers.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailUsers.kt new file mode 100644 index 0000000..73450c1 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/email/EmailUsers.kt @@ -0,0 +1,42 @@ +package cn.bunny.dao.entity.email + +import cn.bunny.dao.entity.BaseEntity +import com.baomidou.mybatisplus.annotation.TableName +import io.swagger.v3.oas.annotations.media.Schema +import lombok.Getter +import lombok.Setter +import lombok.experimental.Accessors + +/** + * + * + * 邮箱发送表 + * + * + * @author Bunny + * @since 2024-05-17 + */ +@Getter +@Setter +@Accessors(chain = true) +@TableName("email_users") +@Schema(name = "EmailUsers对象", title = "邮箱发送表", description = "邮箱发送表") +class EmailUsers : BaseEntity() { + @Schema(name = "email", title = "邮箱") + var email: String? = null + + @Schema(name = "password", title = "密码") + var password: String? = null + + @Schema(name = "host", title = "Host地址") + var host: String? = null + + @Schema(name = "port", title = "端口号") + var port: Int? = null + + @Schema(name = "smtpAgreement", title = "邮箱协议") + var smtpAgreement: String? = null + + @Schema(name = "isDefault", title = "是否为默认邮件") + var isDefault: Int? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18n.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18n.kt new file mode 100644 index 0000000..aa11434 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18n.kt @@ -0,0 +1,31 @@ +package cn.bunny.dao.entity.i18n + +import cn.bunny.dao.entity.BaseEntity +import io.swagger.v3.oas.annotations.media.Schema +import lombok.Getter +import lombok.Setter +import lombok.experimental.Accessors + +/** + * + * + * 多语言表 + * + * + * @author Bunny + * @since 2024-09-26 + */ +@Getter +@Setter +@Accessors(chain = true) +@Schema(name = "I18n对象", title = "多语言表", description = "多语言表") +class I18n : BaseEntity() { + @Schema(name = "typeId", title = "语言类型id") + var typeId: Long? = null + + @Schema(name = "keyName", title = "多语言key") + var keyName: String? = null + + @Schema(name = "summary", title = "翻译") + var summary: String? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18nType.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18nType.kt new file mode 100644 index 0000000..35765b5 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/i18n/I18nType.kt @@ -0,0 +1,33 @@ +package cn.bunny.dao.entity.i18n + +import cn.bunny.dao.entity.BaseEntity +import com.baomidou.mybatisplus.annotation.TableName +import io.swagger.v3.oas.annotations.media.Schema +import lombok.Getter +import lombok.Setter +import lombok.experimental.Accessors + +/** + * + * + * 多语言类型表 + * + * + * @author Bunny + * @since 2024-09-26 + */ +@Getter +@Setter +@Accessors(chain = true) +@TableName("i18n_type") +@Schema(name = "I18nType", title = "多语言类型表", description = "多语言类型表") +class I18nType : BaseEntity() { + @Schema(name = "languageName", title = "语言名称") + var languageName: String? = null + + @Schema(name = "summary", title = "语言名") + var summary: String? = null + + @Schema(name = "isDefault", title = "是否作为默认语言") + var isDefault: Byte? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/entity/user/AdminUser.kt b/dao/src/main/kotlin/cn/bunny/dao/entity/user/AdminUser.kt new file mode 100644 index 0000000..cdf5ec6 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/entity/user/AdminUser.kt @@ -0,0 +1,57 @@ +package cn.bunny.dao.entity.user + +import cn.bunny.dao.entity.BaseEntity +import com.baomidou.mybatisplus.annotation.TableName +import io.swagger.v3.oas.annotations.media.Schema +import lombok.Getter +import lombok.Setter +import lombok.experimental.Accessors + +/** + * + * + * 用户信息 + * + * + * @author Bunny + * @since 2024-09-26 + */ +@Getter +@Setter +@Accessors(chain = true) +@TableName("sys_user") +@Schema(name = "AdminUser对象", title = "用户信息", description = "用户信息") +class AdminUser : BaseEntity() { + @Schema(name = "username", title = "用户名") + var username: String? = null + + @Schema(name = "nickName", title = "昵称") + var nickName: String? = null + + @Schema(name = "email", title = "邮箱") + var email: String? = null + + @Schema(name = "phone", title = "手机号") + var phone: String? = null + + @Schema(name = "password", title = "密码") + var password: String? = null + + @Schema(name = "avatar", title = "头像") + var avatar: String? = null + + @Schema(name = "sex", title = "性别", description = "0:女 1:男") + var sex: Byte? = null + + @Schema(name = "summary", title = "个人描述") + var summary: String? = null + + @Schema(name = "lastLoginIp", title = "最后登录IP") + var lastLoginIp: String? = null + + @Schema(name = "lastLoginIpAddress", title = "最后登录ip归属地") + var lastLoginIpAddress: String? = null + + @Schema(name = "status", title = "状态", description = "1:禁用 0:正常") + var status: Byte? = null +} 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..c351641 --- /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 +class LocalDateTimeConstant { + companion object { + const val YYYY_MM_DD: String = "yyyy-MM-dd" + const val YYYY_MM_DD_HH_MM_SS: String = "yyyy-MM-dd 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..fecead2 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/constant/MailMessageConstant.kt @@ -0,0 +1,17 @@ +package cn.bunny.dao.pojo.constant + +import lombok.Data + +/** + * 邮箱消息 + */ +@Data +class MailMessageConstant { + companion object { + 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..ad08558 --- /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 { + // 管理员用户 + private const val ADMIN_LOGIN_INFO_PREFIX: String = "ADMIN::LOGIN_INFO::" + private const val ADMIN_EMAIL_CODE_PREFIX: String = "ADMIN::EMAIL_CODE::" + + // 普通用户 + private const val USER_LOGIN_INFO_PREFIX: String = "USER::LOGIN_INFO::" + private const val USER_EMAIL_CODE_PREFIX: String = "USER::EMAIL_CODE::" + private 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..54472a6 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSend.kt @@ -0,0 +1,37 @@ +package cn.bunny.dao.pojo.email + +import io.swagger.v3.oas.annotations.media.Schema +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor +import org.springframework.web.multipart.MultipartFile + +/** + * 邮件发送对象 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Schema(name = "EmailSend", title = "邮件发送表单", description = "邮件发送表单") +class EmailSend { + + @Schema(name = "sendTo", title = "给谁发送") + var sendTo: String? = null + + @Schema(name = "subject", title = "发送主题") + var subject: String? = null + + @Schema(name = "isRichText", title = "是否为富文本") + var isRichText: Boolean? = null + + @Schema(name = "message", title = "发送内容") + var message: String? = null + + @Schema(name = "ccParam", title = "抄送人") + var ccParam: String? = null + + @Schema(name = "file", title = "发送的文件") + 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..ed6415b --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/email/EmailSendInit.kt @@ -0,0 +1,31 @@ +package cn.bunny.dao.pojo.email + +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(name = "EmailSendInit", title = "邮件发送初始化", description = "邮件发送初始化") +class EmailSendInit { + + @Schema(name = "port", title = "端口") + var port: Int? = null + + @Schema(name = "host", title = "主机") + var host: String? = null + + @Schema(name = "username", title = "用户名") + var username: String? = null + + @Schema(name = "password", title = "密码") + var password: String? = null + +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.kt new file mode 100644 index 0000000..c14aff3 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/enums/OperationType.kt @@ -0,0 +1,8 @@ +package cn.bunny.dao.pojo.enums + +/** + * 数据库操作类型 + */ +enum class OperationType { + UPDATE, INSERT +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.kt new file mode 100644 index 0000000..5d7cf6f --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/file/MinioFIlePath.kt @@ -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 +class MinioFIlePath { + var filename: String? = null + var uuidFilename: String? = null + var timeUuidFilename: String? = null + var filepath: String? = null + var bucketNameFilepath: String? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.kt new file mode 100644 index 0000000..a45df3a --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/PageResult.kt @@ -0,0 +1,28 @@ +package cn.bunny.dao.pojo.result + +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor +import java.io.Serializable + +/** + * 封装分页查询结果 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +class PageResult : Serializable { + // 当前页 + var pageNo: Int? = null + + // 每页记录数 + var pageSize: Int? = null + + // 总记录数 + var total: Long = 0 + + // 当前页数据集合 + var list: List? = null +} \ 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.kt b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/ResultCodeEnum.kt new file mode 100644 index 0000000..f257b08 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/pojo/result/ResultCodeEnum.kt @@ -0,0 +1,51 @@ +package cn.bunny.dao.pojo.result + +import lombok.Getter + +/** + * 统一返回结果状态信息类 + */ +@Getter +enum class ResultCodeEnum(val code: Int, val message: String) { + // 成功操作 200 + SUCCESS(200, "操作成功"), + SUCCESS_LOGOUT(200, "退出成功"), + EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"), + + // 验证错误 201 + USERNAME_OR_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, "失败"), +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.kt b/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.kt new file mode 100644 index 0000000..e6715c3 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/BaseVo.kt @@ -0,0 +1,46 @@ +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.v3.oas.annotations.media.Schema +import lombok.Data +import java.io.Serializable +import java.time.LocalDateTime + +@Data +@Schema(name = "BaseVo", title = "基础返回对象内容", description = "基础返回对象内容") +open class BaseVo : Serializable { + @Schema(name = "id", title = "主键") + @JsonProperty("id") + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var id: Long? = null + + @Schema(name = "updateTime", title = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer::class) + @JsonDeserialize(using = LocalDateTimeDeserializer::class) + var updateTime: LocalDateTime? = null + + @Schema(name = "createTime", title = "发布时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonSerialize(using = LocalDateTimeSerializer::class) + @JsonDeserialize(using = LocalDateTimeDeserializer::class) + var createTime: LocalDateTime? = null + + @Schema(name = "createUser", title = "创建用户") + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var createUser: Long? = null + + @Schema(name = "updateUser", title = "操作用户") + @JsonFormat(shape = JsonFormat.Shape.STRING) + @JSONField(serializeUsing = ToStringSerializer::class) + var updateUser: Long? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.kt b/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.kt new file mode 100644 index 0000000..9454c60 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/email/EmailTemplateVo.kt @@ -0,0 +1,23 @@ +package cn.bunny.dao.vo.email + +import io.swagger.v3.oas.annotations.media.Schema +import lombok.AllArgsConstructor +import lombok.Builder +import lombok.Data +import lombok.NoArgsConstructor + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "EmailTemplateVo对象", title = "邮箱模板返回内容", description = "邮箱模板返回内容") +class EmailTemplateVo { + @Schema(name = "templateName", title = "模板名称") + var templateName: String? = null + + @Schema(name = "subject", title = "主题") + var subject: String? = null + + @Schema(name = "body", title = "邮件内容") + var body: String? = null +} \ No newline at end of file diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/user/LoginVo.kt b/dao/src/main/kotlin/cn/bunny/dao/vo/user/LoginVo.kt new file mode 100644 index 0000000..9b412d2 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/user/LoginVo.kt @@ -0,0 +1,70 @@ +package cn.bunny.dao.vo.user + +import cn.bunny.dao.vo.BaseVo +import io.swagger.v3.oas.annotations.media.Schema +import lombok.* + +/** + * 用户登录返回内容 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Schema(name = "LoginVo对象", title = "登录成功返回内容", description = "登录成功返回内容") +class LoginVo : BaseVo() { + @Schema(name = "nickName", title = "昵称") + var nickName: String? = null + + @Schema(name = "username", title = "用户名") + var username: String? = null + + @Schema(name = "email", title = "邮箱") + var email: String? = null + + @Schema(name = "phone", title = "手机号") + var phone: String? = null + + @Schema(name = "password", title = "密码") + var password: String? = null + + @Schema(name = "avatar", title = "头像") + var avatar: String? = null + + @Schema(name = "sex", title = "0:女 1:男") + var sex: Byte? = null + + @Schema(name = "personDescription", title = "个人描述") + var personDescription: String? = null + + @Schema(name = "articleMode", title = "文章显示模式") + var articleMode: String? = null + + @Schema(name = "layout", title = "页面布局方式") + var layout: String? = null + + @Schema(name = "lastLoginIp", title = "最后登录IP") + var lastLoginIp: String? = null + + @Schema(name = "lastLoginIpAddress", title = "最后登录ip地址") + var lastLoginIpAddress: String? = null + + @Schema(name = "totalIntegral", title = "积分") + var totalIntegral: Int? = null + + @Schema(name = "currentIntegral", title = "当前积分") + var currentIntegral: Int? = null + + @Schema(name = "status", title = "0:禁用 1:正常") + var status: Boolean? = null + + @Schema(name = "token", title = "令牌") + var token: String? = null + + @Schema(name = "roleList", title = "角色列表") + var roleList: List? = null + + @Schema(name = "powerList", title = "权限列表") + var powerList: List? = null +} diff --git a/dao/src/main/kotlin/cn/bunny/dao/vo/user/ValidateCodeVo.kt b/dao/src/main/kotlin/cn/bunny/dao/vo/user/ValidateCodeVo.kt new file mode 100644 index 0000000..76c91b1 --- /dev/null +++ b/dao/src/main/kotlin/cn/bunny/dao/vo/user/ValidateCodeVo.kt @@ -0,0 +1,20 @@ +package cn.bunny.dao.vo.user + +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(name = "ValidateCodeVo", title = "验证码响应结果实体类", description = "验证码响应结果实体类") +class ValidateCodeVo { + @Schema(name = "codeKey", title = "验证码key") + var codeKey: String? = null + + @Schema(name = "codeValue", title = "验证码value") + var codeValue: String? = null +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..df34d84 --- /dev/null +++ b/pom.xml @@ -0,0 +1,199 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + cn.bunny + kotlin-single + 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..4323c25 --- /dev/null +++ b/services/pom.xml @@ -0,0 +1,182 @@ + + + 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 + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-test + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.amqp + spring-rabbit-test + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.16.0-rc1 + + + + org.aspectj + aspectjrt + + + org.aspectj + aspectjweaver + + + + org.springframework.boot + spring-boot-starter-websocket + + + + com.baomidou + dynamic-datasource-spring-boot3-starter + 4.3.1 + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + + + 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 + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + + ${java.version} + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + + + + 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/controller/I18nController.java b/services/src/main/kotlin/cn/bunny/services/controller/I18nController.java new file mode 100644 index 0000000..2c624c2 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/I18nController.java @@ -0,0 +1,20 @@ +package cn.bunny.services.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 多语言表 前端控制器 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@RestController +@RequestMapping("/i18n") +@Tag(name = "多语言", description = "多语言相关接口") +public class I18nController { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/I18nTypeController.java b/services/src/main/kotlin/cn/bunny/services/controller/I18nTypeController.java new file mode 100644 index 0000000..b380356 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/I18nTypeController.java @@ -0,0 +1,20 @@ +package cn.bunny.services.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 多语言类型表 前端控制器 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@RestController +@RequestMapping("/i18nType") +@Tag(name = "多语言类型", description = "多语言类型相关接口") +public class I18nTypeController { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/IndexController.kt b/services/src/main/kotlin/cn/bunny/services/controller/IndexController.kt new file mode 100644 index 0000000..7411636 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/IndexController.kt @@ -0,0 +1,34 @@ +package cn.bunny.services.controller + +import cn.hutool.captcha.CaptchaUtil +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +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 = "访问首页内容", description = "访问首页内容相关接口") +@RestController +@RequestMapping("/") +class IndexController { + @Operation(summary = "访问首页", description = "访问首页") + @GetMapping("") + fun index(): String { + return "欢迎访问 Bunny Java Template,欢迎去Gitee:https://gitee.com/BunnyBoss/java_single.git" + } + + @Operation(summary = "生成验证码", description = "生成验证码") + @GetMapping("noAuth/checkCode") + fun checkCode(): ResponseEntity { + val headers = HttpHeaders() + headers.contentType = MediaType.IMAGE_JPEG + // 生成验证码 + val captcha = CaptchaUtil.createCircleCaptcha(150, 48, 4, 2) + val image = captcha.imageBytes + return ResponseEntity(image, headers, HttpStatus.OK) + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/controller/UserController.kt b/services/src/main/kotlin/cn/bunny/services/controller/UserController.kt new file mode 100644 index 0000000..8d9ee8d --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/controller/UserController.kt @@ -0,0 +1,19 @@ +package cn.bunny.services.controller + +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +/** + * + * + * 用户信息 前端控制器 + * + * + * @author Bunny + * @since 2024-09-26 + */ +@RestController +@RequestMapping("/user") +@Tag(name = "系统用户", description = "系统用户相关接口") +class UserController diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/I18nMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/I18nMapper.java new file mode 100644 index 0000000..dae6df7 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/I18nMapper.java @@ -0,0 +1,18 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.i18n.I18n; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 多语言表 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Mapper +public interface I18nMapper extends BaseMapper { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/mapper/I18nTypeMapper.java b/services/src/main/kotlin/cn/bunny/services/mapper/I18nTypeMapper.java new file mode 100644 index 0000000..53e40fc --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/I18nTypeMapper.java @@ -0,0 +1,18 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.i18n.I18nType; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 多语言类型表 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Mapper +public interface I18nTypeMapper 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..5957bda --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package cn.bunny.services.mapper; + +import cn.bunny.dao.entity.user.AdminUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 用户信息 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Mapper +public interface UserMapper extends BaseMapper { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/config/WebSecurityConfig.kt b/services/src/main/kotlin/cn/bunny/services/security/config/WebSecurityConfig.kt new file mode 100644 index 0000000..84c75c1 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/config/WebSecurityConfig.kt @@ -0,0 +1,102 @@ +package cn.bunny.services.security.config + +import cn.bunny.services.security.custom.CustomPasswordEncoder +import cn.bunny.services.security.filter.TokenAuthenticationFilter +import cn.bunny.services.security.filter.TokenLoginFilterService +import cn.bunny.services.security.handelr.SecurityAccessDeniedHandler +import cn.bunny.services.security.handelr.SecurityAuthenticationEntryPoint +import cn.bunny.services.security.service.CustomAuthorizationManagerService +import cn.bunny.services.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.requestMatchers(RegexRequestMatcher.regexMatcher("^.*\\.(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 + fun sessionRegistry(): SessionRegistry { + return SessionRegistryImpl() + } + + // 排出鉴定路径 + @Bean + open fun webSecurityCustomizer(): WebSecurityCustomizer { + val annotations = arrayOf( + "/", "/ws/**", + "/*/*/noAuth/**", "/*/noAuth/**", "/noAuth/**", + "/favicon.ico", "*.html", + "/swagger-resources/**", "/v3/**", "/swagger-ui/**" + ) + return WebSecurityCustomizer { web: WebSecurity -> web.ignoring().requestMatchers(*annotations) } + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/custom/CustomPasswordEncoder.kt b/services/src/main/kotlin/cn/bunny/services/security/custom/CustomPasswordEncoder.kt new file mode 100644 index 0000000..e459f58 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/custom/CustomPasswordEncoder.kt @@ -0,0 +1,25 @@ +package cn.bunny.services.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 +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/services/src/main/kotlin/cn/bunny/services/security/custom/CustomUser.kt b/services/src/main/kotlin/cn/bunny/services/security/custom/CustomUser.kt new file mode 100644 index 0000000..ccbbc07 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/custom/CustomUser.kt @@ -0,0 +1,15 @@ +package cn.bunny.services.security.custom + +import cn.bunny.dao.entity.user.AdminUser +import lombok.Getter +import lombok.Setter +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.userdetails.User + +/** + * 重写自带的User + */ +@Getter +@Setter +class CustomUser(user: AdminUser, authorities: Collection?) : + User(user.email, user.password, authorities) \ No newline at end of file diff --git a/services/src/main/kotlin/cn/bunny/services/security/filter/TokenAuthenticationFilter.kt b/services/src/main/kotlin/cn/bunny/services/security/filter/TokenAuthenticationFilter.kt new file mode 100644 index 0000000..4baf974 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/filter/TokenAuthenticationFilter.kt @@ -0,0 +1,59 @@ +package cn.bunny.services.security.filter + +import cn.bunny.common.service.exception.BunnyException +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.web.filter.OncePerRequestFilter +import java.io.IOException +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") + + // 判断是否有token,如果后面还有过滤器就这样写 + // if (token == null) { + // doFilter(request, response, chain) + // return + // } + + + // 自定义实现内容 + val authentication = getAuthentication(request) + if (authentication != null) { + // 设置用户详细信息 + 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 = "admin" + val authList: MutableList = ArrayList() + + // 设置角色内容 + if (token != null) { + val roleList = ArrayList() + roleList.forEach(Consumer { role: String? -> authList.add(SimpleGrantedAuthority(role)) }) + return UsernamePasswordAuthenticationToken(username, null, authList) + } else { + return UsernamePasswordAuthenticationToken(username, null, ArrayList()) + } + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/filter/TokenLoginFilterService.kt b/services/src/main/kotlin/cn/bunny/services/security/filter/TokenLoginFilterService.kt new file mode 100644 index 0000000..2b2e15c --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/filter/TokenLoginFilterService.kt @@ -0,0 +1,124 @@ +package cn.bunny.services.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.services.security.handelr.SecurityAuthenticationFailureHandler +import cn.bunny.services.security.handelr.SecurityAuthenticationSuccessHandler +import cn.bunny.services.security.service.CustomUserDetailsService +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 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 { + // 获取用户信息相关内容 + val loginDto = LoginDto() + loginDto.username = "admin" + loginDto.password = "password" + + // 封装对象,将用户名密码传入 + 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 + ) { + val password = loginDto.password + val username = loginDto.username + + when { + password.isNullOrBlank() || username.isNullOrBlank() -> out(response, Result.error(ResultCodeEnum.USERNAME_OR_PASSWORD_NOT_EMPTY)) + else -> out(response, Result.error(null, ResultCodeEnum.LOGIN_ERROR)) + } + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAccessDeniedHandler.kt b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAccessDeniedHandler.kt new file mode 100644 index 0000000..5dd86ad --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAccessDeniedHandler.kt @@ -0,0 +1,30 @@ +package cn.bunny.services.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/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationEntryPoint.kt b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationEntryPoint.kt new file mode 100644 index 0000000..7235757 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationEntryPoint.kt @@ -0,0 +1,44 @@ +package cn.bunny.services.security.handelr + +import cn.bunny.common.service.utils.JwtHelper +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 + +/** + * 请求未认证接口 + */ +class SecurityAuthenticationEntryPoint : AuthenticationEntryPoint { + private val logger = LogManager.getLogger(SecurityAuthenticationEntryPoint::class.java) + + override fun commence( + request: HttpServletRequest, + response: HttpServletResponse, + authException: AuthenticationException + ) { + val token = response.getHeader("token") + val message = authException.message + // 创建结果对象 + val result: Result = when { + token.isNullOrEmpty() -> Result.error(ResultCodeEnum.LOGIN_AUTH) + + JwtHelper.isExpired(token) -> { + logger.info("登录Token过期:{},用户id:{}", message, null) + Result.error(ResultCodeEnum.AUTHENTICATION_EXPIRED) + } + + else -> { + logger.info("请求未授权接口:{},用户id:{}", message, token) + Result.error(ResultCodeEnum.LOGGED_IN_FROM_ANOTHER_DEVICE) + } + } + + // 返回响应 + out(response, result) + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationFailureHandler.kt b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationFailureHandler.kt new file mode 100644 index 0000000..75543eb --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationFailureHandler.kt @@ -0,0 +1,30 @@ +package cn.bunny.services.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/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationSuccessHandler.kt b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationSuccessHandler.kt new file mode 100644 index 0000000..0ae3df6 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/handelr/SecurityAuthenticationSuccessHandler.kt @@ -0,0 +1,29 @@ +package cn.bunny.services.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/services/src/main/kotlin/cn/bunny/services/security/service/CustomAuthorizationManagerService.kt b/services/src/main/kotlin/cn/bunny/services/security/service/CustomAuthorizationManagerService.kt new file mode 100644 index 0000000..f3ce3b2 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/service/CustomAuthorizationManagerService.kt @@ -0,0 +1,6 @@ +package cn.bunny.services.security.service + +import org.springframework.security.authorization.AuthorizationManager +import org.springframework.security.web.access.intercept.RequestAuthorizationContext + +interface CustomAuthorizationManagerService : AuthorizationManager diff --git a/services/src/main/kotlin/cn/bunny/services/security/service/CustomUserDetailsService.kt b/services/src/main/kotlin/cn/bunny/services/security/service/CustomUserDetailsService.kt new file mode 100644 index 0000000..5867c43 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/service/CustomUserDetailsService.kt @@ -0,0 +1,23 @@ +package cn.bunny.services.security.service + +import cn.bunny.dao.dto.user.LoginDto +import cn.bunny.dao.vo.user.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/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomAuthorizationManagerServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomAuthorizationManagerServiceImpl.java new file mode 100644 index 0000000..7eb09f5 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomAuthorizationManagerServiceImpl.java @@ -0,0 +1,57 @@ +package cn.bunny.services.security.service.impl; + +import cn.bunny.common.service.utils.JwtHelper; +import cn.bunny.services.security.service.CustomAuthorizationManagerService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +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 { + + @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"); + // 用户id + Long userId = JwtHelper.getUserId(token); + // 请求地址 + String requestURI = request.getRequestURI(); + // 请求方式 + String method = request.getMethod(); + // 角色代码列表 + List roleCodeList = authentication.get().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList(); + + return new AuthorizationDecision(hasRoleList(requestURI, method, userId)); + } + + /** + * 查询用户所属的角色信息 + * + * @param requestURI 请求url地址 + * @param method 请求方式 + * @param userId 用户id + */ + private Boolean hasRoleList(String requestURI, String method, Long userId) { + return true; + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomUserDetailsServiceImpl.kt b/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomUserDetailsServiceImpl.kt new file mode 100644 index 0000000..52fc37a --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/security/service/impl/CustomUserDetailsServiceImpl.kt @@ -0,0 +1,47 @@ +package cn.bunny.services.security.service.impl + +import cn.bunny.dao.dto.user.LoginDto +import cn.bunny.dao.entity.user.AdminUser +import cn.bunny.dao.vo.user.LoginVo +import cn.bunny.services.mapper.UserMapper +import cn.bunny.services.security.custom.CustomUser +import cn.bunny.services.security.service.CustomUserDetailsService +import com.baomidou.mybatisplus.core.toolkit.Wrappers +import com.baomidou.mybatisplus.core.toolkit.support.SFunction +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 + +@Component +class CustomUserDetailsServiceImpl : CustomUserDetailsService { + @Autowired + private val userMapper: UserMapper? = null + + @Throws(UsernameNotFoundException::class) + override fun loadUserByUsername(username: String): UserDetails { + // 根据邮箱查询用户名 + val user = userMapper!!.selectOne( + Wrappers.lambdaQuery().eq(SFunction { obj: AdminUser -> obj.email }, username) + ) + // 都为空抛出异常,用户不存在 + if (user == null) { + throw UsernameNotFoundException("") + } + + // 查询所有的角色 + return CustomUser(user, AuthorityUtils.createAuthorityList(listOf("admin", "common"))) + } + + /** + * 前台用户登录接口 + * + * @param loginDto 登录参数 + * @return 登录后结果返回 + */ + override fun login(loginDto: LoginDto): LoginVo { + // 自定义登录逻辑 + return LoginVo() + } +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/I18nService.java b/services/src/main/kotlin/cn/bunny/services/service/I18nService.java new file mode 100644 index 0000000..37a209a --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/I18nService.java @@ -0,0 +1,16 @@ +package cn.bunny.services.service; + +import cn.bunny.dao.entity.i18n.I18n; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 多语言表 服务类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +public interface I18nService extends IService { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/I18nTypeService.java b/services/src/main/kotlin/cn/bunny/services/service/I18nTypeService.java new file mode 100644 index 0000000..db0c25a --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/I18nTypeService.java @@ -0,0 +1,16 @@ +package cn.bunny.services.service; + +import cn.bunny.dao.entity.i18n.I18nType; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 多语言类型表 服务类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +public interface I18nTypeService extends IService { + +} 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..9b19acc --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/UserService.java @@ -0,0 +1,16 @@ +package cn.bunny.services.service; + +import cn.bunny.dao.entity.user.AdminUser; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 用户信息 服务类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +public interface UserService extends IService { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/impl/I18nServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/service/impl/I18nServiceImpl.java new file mode 100644 index 0000000..2ebd537 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/impl/I18nServiceImpl.java @@ -0,0 +1,20 @@ +package cn.bunny.services.service.impl; + +import cn.bunny.dao.entity.i18n.I18n; +import cn.bunny.services.mapper.I18nMapper; +import cn.bunny.services.service.I18nService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 多语言表 服务实现类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Service +public class I18nServiceImpl extends ServiceImpl implements I18nService { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/impl/I18nTypeServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/service/impl/I18nTypeServiceImpl.java new file mode 100644 index 0000000..5bc8c5e --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/impl/I18nTypeServiceImpl.java @@ -0,0 +1,20 @@ +package cn.bunny.services.service.impl; + +import cn.bunny.dao.entity.i18n.I18nType; +import cn.bunny.services.mapper.I18nTypeMapper; +import cn.bunny.services.service.I18nTypeService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 多语言类型表 服务实现类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Service +public class I18nTypeServiceImpl extends ServiceImpl implements I18nTypeService { + +} diff --git a/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.java b/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..2744591 --- /dev/null +++ b/services/src/main/kotlin/cn/bunny/services/service/impl/UserServiceImpl.java @@ -0,0 +1,20 @@ +package cn.bunny.services.service.impl; + +import cn.bunny.dao.entity.user.AdminUser; +import cn.bunny.services.mapper.UserMapper; +import cn.bunny.services.service.UserService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 用户信息 服务实现类 + *

+ * + * @author Bunny + * @since 2024-09-26 + */ +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + +} diff --git a/services/src/main/resources/application-dev.yml b/services/src/main/resources/application-dev.yml new file mode 100644 index 0000000..74a6699 --- /dev/null +++ b/services/src/main/resources/application-dev.yml @@ -0,0 +1,25 @@ +bunny: + datasource1: + host: 192.168.3.98 + port: 3304 + sqlData: auth_admin + username: root + password: "02120212" + datasource2: + host: 106.15.251.123 + port: 3304 + sqlData: auth_admin_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..11b23c3 --- /dev/null +++ b/services/src/main/resources/application-prod.yml @@ -0,0 +1,25 @@ +bunny: + datasource: + host: 192.168.3.98 + port: 3304 + sqlData: bunny_docs + username: root + password: "02120212" + datasource2: + host: 192.168.3.98 + port: 3304 + 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.yml b/services/src/main/resources/application.yml new file mode 100644 index 0000000..32f0599 --- /dev/null +++ b/services/src/main/resources/application.yml @@ -0,0 +1,79 @@ +server: + port: 8800 +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.datasource1.host}:${bunny.datasource1.port}/${bunny.datasource1.sqlData}?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&allowPublicKeyRetrieval=true + username: ${bunny.datasource1.username} + password: ${bunny.datasource1.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/I18nMapper.xml b/services/src/main/resources/mapper/I18nMapper.xml new file mode 100644 index 0000000..c8184aa --- /dev/null +++ b/services/src/main/resources/mapper/I18nMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, type_id, key_name, summary, create_user, create_time, update_time, update_user, is_deleted + + + diff --git a/services/src/main/resources/mapper/I18nTypeMapper.xml b/services/src/main/resources/mapper/I18nTypeMapper.xml new file mode 100644 index 0000000..1d5347d --- /dev/null +++ b/services/src/main/resources/mapper/I18nTypeMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, language_name, summary, is_default, create_user, 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..a0ef60a --- /dev/null +++ b/services/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, username, nick_name, email, phone, password, avatar, sex, summary, last_login_ip, last_login_ip_address, status, create_user, create_time, update_time, update_user, is_deleted + + + 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