This commit is contained in:
bunny 2024-09-26 09:38:54 +08:00
commit 1c8238bbb7
93 changed files with 4357 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
services/HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common-generator</artifactId>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<!-- 数据库代码生成器 - 新版 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,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<String>) {
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()
}
}
}

View File

@ -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<String>) {
AdminGenerator.generation("i18n", "i18n_type")
}
}
}

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>common-service</artifactId>
<dependencies>
<!-- 解决 javax.xml.bind 错误 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redisson 分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.26.1</version>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 查询ip地址 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.6.5</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
package cn.bunny.common.service.config
import io.swagger.v3.oas.models.ExternalDocumentation
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Contact
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.info.License
import lombok.extern.slf4j.Slf4j
import org.springdoc.core.models.GroupedOpenApi
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
@Slf4j
open class Knife4jConfig {
@Bean
open fun openAPI(): OpenAPI {
// 作者等信息
val contact = Contact().name("Bunny").email("1319900154@qq.com").url("https://kotlinlang.org/docs")
// 使用协议
val license = License().name("MIT").url("https://MUT.com")
// 相关信息
val info = Info().title("Bunny-Java-Template").description("Bunny的Java模板").version("v1.0.0").contact(contact)
.license(license).termsOfService("维护不易~求个start")
return OpenAPI().info(info).externalDocs(ExternalDocumentation())
}
// 前台相关分类接口
@Bean
open fun groupedOpenApi(): GroupedOpenApi {
return GroupedOpenApi.builder().group("web前台接口管理").pathsToMatch("/api/**").build()
}
// 管理员相关分类接口
@Bean
open fun groupedOpenAdminApi(): GroupedOpenApi {
return GroupedOpenApi.builder().group("admin管理员接口请求").pathsToMatch("/admin/**").build()
}
}

View File

@ -0,0 +1,34 @@
package cn.bunny.common.service.config
import cn.bunny.common.service.context.BaseContext
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
import org.apache.ibatis.reflection.MetaObject
import org.springframework.stereotype.Component
import java.time.LocalDateTime
/**
* 配置MP在修改和新增时的操作
*/
@Component
class MyBatisPlusFieldConfig : MetaObjectHandler {
/**
* 使用mp做添加操作时候这个方法执行
*/
override fun insertFill(metaObject: MetaObject) {
this.strictInsertFill(metaObject, "isDeleted", Int::class.java, 0)
this.strictInsertFill(metaObject, "createTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictInsertFill(metaObject, "createUser", Long::class.java, BaseContext.getUserId())
this.strictInsertFill(metaObject, "updateUser", Long::class.java, BaseContext.getUserId())
}
/**
* 使用mp做修改操作时候这个方法执行
*/
override fun updateFill(metaObject: MetaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::class.java, LocalDateTime.now())
this.strictUpdateFill(metaObject, "updateUser", Long::class.java, BaseContext.getUserId())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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<Any?> {
logger.error("GlobalExceptionHandler===>自定义异常信息:{}", exception.message)
val code = if (exception.code != null) exception.code else 500
return Result.error(null, code, exception.message)
}
// 运行时异常信息
@ExceptionHandler(RuntimeException::class)
@ResponseBody
@Throws(FileNotFoundException::class)
fun exceptionHandler(exception: RuntimeException): Result<Any?> {
logger.error("GlobalExceptionHandler===>运行时异常信息:{}", exception.message)
exception.printStackTrace()
return Result.error(null, 500, "出错了啦")
}
// 捕获系统异常
@ExceptionHandler(Exception::class)
@ResponseBody
fun error(exception: Exception): Result<Any?> {
logger.error("GlobalExceptionHandler===>系统异常信息:{}", exception.message)
// 错误消息
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<Any?> {
logger.error("GlobalExceptionHandler===>特定异常信息:{}", exception.message)
return Result.error(null, 500, exception.message)
}
// spring security异常
@ExceptionHandler(AccessDeniedException::class)
@ResponseBody
@Throws(
AccessDeniedException::class
)
fun error(exception: AccessDeniedException): Result<String?> {
logger.error("GlobalExceptionHandler===>spring security异常{}", exception.message)
return Result.error(ResultCodeEnum.SERVICE_ERROR)
}
// 处理SQL异常
@ExceptionHandler(SQLIntegrityConstraintViolationException::class)
@ResponseBody
fun exceptionHandler(exception: SQLIntegrityConstraintViolationException): Result<String> {
logger.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.message)
val message = exception.message
if (message!!.contains("Duplicate entry")) {
// 截取用户名
val username = message.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[2]
// 错误信息
val errorMessage = username + ExceptionConstant.ALREADY_USER_EXCEPTION
return Result.error(errorMessage)
} else {
return Result.error(ExceptionConstant.UNKNOWN_EXCEPTION)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
package cn.bunny.common.service.utils
import cn.bunny.dao.pojo.result.Result
import cn.bunny.dao.pojo.result.ResultCodeEnum
import jakarta.servlet.http.HttpServletResponse
class ResponseHandlerUtil {
companion object {
fun loginAuthHandler(response: HttpServletResponse, loginAuth: ResultCodeEnum): Boolean {
ResponseUtil.out(response, Result.error(loginAuth))
return false
}
}
}

View File

@ -0,0 +1,29 @@
package cn.bunny.common.service.utils
import cn.bunny.dao.pojo.email.EmailSend
import cn.bunny.dao.pojo.result.Result
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import java.io.IOException
open class ResponseUtil {
companion object {
@JvmStatic
fun out(response: HttpServletResponse, result: Result<Any?>?) {
try {
val mapper = ObjectMapper()
// 注册JavaTimeModule模块
mapper.registerModule(JavaTimeModule())
response.contentType = "application/json;charset=UTF-8"
response.status = HttpStatus.OK.value()
mapper.writeValue(response.writer, result)
val emailSend = EmailSend()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

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

View File

@ -0,0 +1,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()
}
}

View File

@ -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操作工具类 简化操作步骤
* ByBunny0212
*/
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioProperties properties;
@Autowired
private MinioClient minioClient;
/**
* 获取Minio文件路径
*/
public static MinioFIlePath getMinioFilePath(String buckName, String minioPreType, MultipartFile file) {
String uuid = UUID.randomUUID().toString();
// 定义日期时间格式
LocalDateTime currentDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM-dd");
String extension = "";
// 原始文件名
String filename = file.getOriginalFilename();
if (StringUtils.hasText(filename) && filename.contains(".")) {
extension = "." + filename.substring(filename.lastIndexOf(".") + 1);
}
// UUID防止重名
String uuidFilename = uuid + extension;
// 拼接时间+UUID文件名
String timeUuidFilename = currentDateTime.format(formatter) + "/" + uuidFilename;// 加上时间路径
// 上传根文件夹+拼接时间+UUID文件名
String filepath = MinioConstant.getType(minioPreType) + timeUuidFilename;
// 桶名称+上传根文件夹+拼接时间+UUID文件名
String buckNameFilepath = "/" + buckName + MinioConstant.getType(minioPreType) + timeUuidFilename;
// 设置及Minio基础信息
MinioFIlePath minioFIlePath = new MinioFIlePath();
minioFIlePath.setFilename(filename);
minioFIlePath.setUuidFilename(uuidFilename);
minioFIlePath.setTimeUuidFilename(timeUuidFilename);
minioFIlePath.setFilepath(filepath);
minioFIlePath.setBucketNameFilepath(buckNameFilepath);
return minioFIlePath;
}
/**
* * 上传文件并返回处理信息
*/
public MinioFIlePath getUploadMinioObjectFilePath(MultipartFile file, String minioPreType) throws IOException {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
if (file != null) {
MinioFIlePath minioFile = getMinioFilePath(bucketName, minioPreType, file);
String filepath = minioFile.getFilepath();
// 上传对象
putObject(bucketName, filepath, file.getInputStream(), file.getSize());
// 设置图片地址
return minioFile;
}
return null;
}
/**
* 获取默认bucket文件并返回字节数组
*
* @param objectName 对象名称
* @return 文件流对象
*/
public byte[] getBucketObjectByte(String objectName) {
// 如果buckName为空设置为默认的桶
String bucketName = properties.getBucketName();
try {
objectName = objectName.replace("/" + bucketName, "");
GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
return getObjectResponse.readAllBytes();
} catch (Exception exception) {
exception.getStackTrace();
}
throw new BunnyException(FileMessageConstant.GET_BUCKET_EXCEPTION);
}
/**
* 获取Minio全路径名Object带有桶名称
*
* @param objectName 对象名称
* @return 全路径
*/
public String getObjectNameFullPath(String objectName) {
String url = properties.getEndpointUrl();
return url + objectName;
}
/**
* 上传文件
*
* @param bucketName 桶名称
* @param filename 文件名
* @param inputStream 输入流
* @param size 大小
*/
public void putObject(String bucketName, String filename, InputStream inputStream, Long size) {
try {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(filename).stream(inputStream, size, -1).build());
} catch (Exception exception) {
exception.getStackTrace();
throw new BunnyException(FileMessageConstant.FILE_UPLOAD_EXCEPTION);
}
}
}

64
common/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>kotlin-single</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>common</name>
<url>https://maven.apache.org</url>
<modules>
<module>common-generator</module>
<module>common-service</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencies>
<!-- dao 层 -->
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mysql连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mysql连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
</project>

129
dao/pom.xml Normal file
View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>kotlin-single</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dao</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- 实体类注解 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.14</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

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

View File

@ -0,0 +1,11 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
@Data
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"
}
}

View File

@ -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 = "邮箱配置为空"
}
}

View File

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

View File

@ -0,0 +1,75 @@
package cn.bunny.dao.pojo.constant
import lombok.Data
/**
* Redis用户前缀设置
*/
@Data
class RedisUserConstant {
companion object {
// 管理员用户
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
}
}
}

View File

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

View File

@ -0,0 +1,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<MultipartFile>? = null
}

View File

@ -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
}

View File

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

View File

@ -0,0 +1,18 @@
package cn.bunny.dao.pojo.file
import lombok.AllArgsConstructor
import lombok.Builder
import lombok.Data
import lombok.NoArgsConstructor
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
class MinioFIlePath {
var filename: String? = null
var uuidFilename: String? = null
var timeUuidFilename: String? = null
var filepath: String? = null
var bucketNameFilepath: String? = null
}

View File

@ -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<T> : Serializable {
// 当前页
var pageNo: Int? = null
// 每页记录数
var pageSize: Int? = null
// 总记录数
var total: Long = 0
// 当前页数据集合
var list: List<T>? = null
}

View File

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

View File

@ -0,0 +1,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, "失败"),
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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<String>? = null
@Schema(name = "powerList", title = "权限列表")
var powerList: List<String>? = null
}

View File

@ -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
}

199
pom.xml Normal file
View File

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

21
services/Dockerfile Normal file
View File

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

182
services/pom.xml Normal file
View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.bunny</groupId>
<artifactId>services</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>services</name>
<description>services</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>17</kotlin.compiler.jvmTarget>
<java.version>17</java.version>
<kotlin.version>2.0.20</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>common-service</artifactId>
<version>1.0.0</version>
</dependency>
<!-- spring-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
<!-- amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.0-rc1</version>
</dependency>
<!-- asp 切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 多数据库源插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<String>) {
runApplication<ServicesApplication>(*args)
}
}
}

View File

@ -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;
/**
* <p>
* 多语言表 前端控制器
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@RestController
@RequestMapping("/i18n")
@Tag(name = "多语言", description = "多语言相关接口")
public class I18nController {
}

View File

@ -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;
/**
* <p>
* 多语言类型表 前端控制器
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@RestController
@RequestMapping("/i18nType")
@Tag(name = "多语言类型", description = "多语言类型相关接口")
public class I18nTypeController {
}

View File

@ -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欢迎去Giteehttps://gitee.com/BunnyBoss/java_single.git"
}
@Operation(summary = "生成验证码", description = "生成验证码")
@GetMapping("noAuth/checkCode")
fun checkCode(): ResponseEntity<ByteArray> {
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)
}
}

View File

@ -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

View File

@ -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;
/**
* <p>
* 多语言表 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Mapper
public interface I18nMapper extends BaseMapper<I18n> {
}

View File

@ -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;
/**
* <p>
* 多语言类型表 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Mapper
public interface I18nTypeMapper extends BaseMapper<I18nType> {
}

View File

@ -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;
/**
* <p>
* 用户信息 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Mapper
public interface UserMapper extends BaseMapper<AdminUser> {
}

View File

@ -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<Any, Any>? = null
// 自定义用户接口
@Autowired
private val customUserDetailsService: CustomUserDetailsService? = null
// 自定义密码加密器
@Autowired
private val customPasswordEncoder: CustomPasswordEncoder? = null
// 自定义验证码
@Autowired
private val customAuthorizationManager: CustomAuthorizationManagerService? = null
@Autowired
private val authenticationConfiguration: AuthenticationConfiguration? = null
@Bean
@Throws(Exception::class)
open fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity // 前端段分离不需要---禁用明文验证
.httpBasic { obj: HttpBasicConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---禁用默认登录页
.formLogin { obj: FormLoginConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---禁用退出页
.logout { obj: LogoutConfigurer<HttpSecurity> -> obj.disable() } // 前端段分离不需要---csrf攻击
.csrf { obj: CsrfConfigurer<HttpSecurity> -> obj.disable() } // 跨域访问权限,如果需要可以关闭后自己配置跨域访问
.cors { obj: CorsConfigurer<HttpSecurity> -> obj.disable() } // 前后端分离不需要---因为是无状态的
.sessionManagement { obj: SessionManagementConfigurer<HttpSecurity> -> obj.disable() } // 前后端分离不需要---记住我e -> e.rememberMeParameter("rememberBunny").rememberMeCookieName("rememberBunny").key("BunnyKey")
.rememberMe { obj: RememberMeConfigurer<HttpSecurity> -> obj.disable() }
.authorizeHttpRequests { authorize ->
// 有样式文件,不需要访问权限
// authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^\\S*[css|js]$")).permitAll()
authorize.requestMatchers(RegexRequestMatcher.regexMatcher("^.*\\.(css|js)$")).permitAll()
// 上面都不是需要鉴权访问
authorize.anyRequest().access(customAuthorizationManager)
}
.exceptionHandling { exception: ExceptionHandlingConfigurer<HttpSecurity?> ->
// 请求未授权接口
exception.authenticationEntryPoint(SecurityAuthenticationEntryPoint())
// 没有权限访问
exception.accessDeniedHandler(SecurityAccessDeniedHandler())
} // 登录验证过滤器
.addFilterBefore(
TokenLoginFilterService(authenticationConfiguration!!, redisTemplate!!, customUserDetailsService!!),
UsernamePasswordAuthenticationFilter::class.java
) // 其它权限鉴权过滤器
.addFilterAt(
TokenAuthenticationFilter(redisTemplate),
UsernamePasswordAuthenticationFilter::class.java
) // 自定义密码加密器和用户登录
.passwordManagement(customPasswordEncoder).userDetailsService(customUserDetailsService)
return httpSecurity.build()
}
@Bean
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) }
}
}

View File

@ -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<PasswordManagementConfigurer<HttpSecurity?>?> {
override fun encode(rawPassword: CharSequence): String {
return DigestUtils.md5DigestAsHex(rawPassword.toString().toByteArray())
}
override fun matches(rawPassword: CharSequence, encodedPassword: String): Boolean {
return encodedPassword.matches(DigestUtils.md5DigestAsHex(rawPassword.toString().toByteArray()).toRegex())
}
override fun customize(httpSecurityPasswordManagementConfigurer: PasswordManagementConfigurer<HttpSecurity?>?) {
}
}

View File

@ -0,0 +1,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<GrantedAuthority?>?) :
User(user.email, user.password, authorities)

View File

@ -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<Any, Any>) : 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<SimpleGrantedAuthority> = ArrayList()
// 设置角色内容
if (token != null) {
val roleList = ArrayList<String>()
roleList.forEach(Consumer { role: String? -> authList.add(SimpleGrantedAuthority(role)) })
return UsernamePasswordAuthenticationToken(username, null, authList)
} else {
return UsernamePasswordAuthenticationToken(username, null, ArrayList())
}
}
}

View File

@ -0,0 +1,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的登录只能是表单形式 并且用户名密码需要时usernamepassword,可以通过继承 UsernamePasswordAuthenticationFilter 获取登录请求的参数
* 再去设置到 UsernamePasswordAuthenticationToken 来改变请求传参方式参数名等 或者也可以在登录的时候加入其他参数等等
*/
class TokenLoginFilterService(
authenticationConfiguration: AuthenticationConfiguration,
redisTemplate: RedisTemplate<Any, Any>,
customUserDetailsService: CustomUserDetailsService
) : UsernamePasswordAuthenticationFilter() {
private val redisTemplate: RedisTemplate<Any, Any>
private val customUserDetailsService: CustomUserDetailsService
private lateinit var loginDto: LoginDto
// 依赖注入
init {
this.setAuthenticationSuccessHandler(SecurityAuthenticationSuccessHandler())
this.setAuthenticationFailureHandler(SecurityAuthenticationFailureHandler())
this.setPostOnly(false)
// ? 指定登录接口及提交方式,可以指定任意路径
this.setRequiresAuthenticationRequestMatcher(AntPathRequestMatcher("/*/login", HttpMethod.POST.name()))
this.authenticationManager = authenticationConfiguration.authenticationManager
// 依赖注入
this.redisTemplate = redisTemplate
this.customUserDetailsService = customUserDetailsService
}
/**
* * 登录认证获取输入的用户名和密码调用方法认证
* 接受前端login登录参数
* 在这里可以设置短信验证登录
*/
@Throws(AuthenticationException::class)
override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse): Authentication? {
try {
// 获取用户信息相关内容
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))
}
}
}

View File

@ -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<Any>(ResultCodeEnum.FAIL_NO_ACCESS_DENIED)
val json = JSON.toJSON(result)
// 返回响应
response.contentType = "application/json;charset=UTF-8"
response.writer.println(json)
}
}

View File

@ -0,0 +1,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<Any?> = 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)
}
}

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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<RequestAuthorizationContext?>

View File

@ -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
}

View File

@ -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> authentication, RequestAuthorizationContext requestAuthorizationContext) {
CustomAuthorizationManagerService.super.verify(authentication, requestAuthorizationContext);
}
@Override
public AuthorizationDecision check(Supplier<Authentication> 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<String> 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;
}
}

View File

@ -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<AdminUser>().eq(SFunction<AdminUser, Any> { 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()
}
}

View File

@ -0,0 +1,16 @@
package cn.bunny.services.service;
import cn.bunny.dao.entity.i18n.I18n;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 多语言表 服务类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
public interface I18nService extends IService<I18n> {
}

View File

@ -0,0 +1,16 @@
package cn.bunny.services.service;
import cn.bunny.dao.entity.i18n.I18nType;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 多语言类型表 服务类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
public interface I18nTypeService extends IService<I18nType> {
}

View File

@ -0,0 +1,16 @@
package cn.bunny.services.service;
import cn.bunny.dao.entity.user.AdminUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户信息 服务类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
public interface UserService extends IService<AdminUser> {
}

View File

@ -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;
/**
* <p>
* 多语言表 服务实现类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Service
public class I18nServiceImpl extends ServiceImpl<I18nMapper, I18n> implements I18nService {
}

View File

@ -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;
/**
* <p>
* 多语言类型表 服务实现类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Service
public class I18nTypeServiceImpl extends ServiceImpl<I18nTypeMapper, I18nType> implements I18nTypeService {
}

View File

@ -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;
/**
* <p>
* 用户信息 服务实现类
* </p>
*
* @author Bunny
* @since 2024-09-26
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implements UserService {
}

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -0,0 +1,6 @@
__________ ____ __. __ .__ .__
\______ \__ __ ____ ____ ___.__. | |/ _|_____/ |_| | |__| ____
| | _/ | \/ \ / < | | | < / _ \ __\ | | |/ \
| | \ | / | \ | \___ | | | ( <_> ) | | |_| | | \
|______ /____/|___| /___| / ____| |____|__ \____/|__| |____/__|___| /
\/ \/ \/\/ \/ \/

Binary file not shown.

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false" xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback
https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
<appender name="STOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%cyan([%thread]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line) - %blue(%msg%n)
</pattern>
</encoder>
</appender>
<!-- additivity:false 禁止重复打印日志 -->
<!-- 让SpringBoot内部日志ERROR级别 减少日志输出 -->
<logger name="org.springframework" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让mybatis整合包日志ERROR 减少日志输出 -->
<logger name="org.mybatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让ibatis 日志ERROR 减少日志输出 -->
<logger name="org.apache.ibatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让 tomcat包打印日志 日志ERROR 减少日志输出 -->
<logger name="org.apache" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 我们自己开发的程序为DEBUG -->
<logger name="com.redpig" level="DEBUG" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<logger name="com.baomidou" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<logger name="com.zaxxer" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- Activiti日志 -->
<logger name="org.activiti" level="ERROR" />
<logger name="org.activiti.engine.impl.persistence.entity" level="DEBUG" />
<logger name="_org.springframework" level="ERROR" />
<logger name="springfox.documentation" level="ERROR" />
<!-- root级别开debug 子目录根据需要关闭 -->
<root level="DEBUG">
<appender-ref ref="STOUT"/>
</root>
</configuration>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bunny.services.mapper.I18nMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.bunny.dao.entity.i18n.I18n">
<id column="id" property="id"/>
<result column="type_id" property="typeId"/>
<result column="key_name" property="keyName"/>
<result column="summary" property="summary"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="update_user" property="updateUser"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, type_id, key_name, summary, create_user, create_time, update_time, update_user, is_deleted
</sql>
</mapper>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bunny.services.mapper.I18nTypeMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.bunny.dao.entity.i18n.I18nType">
<id column="id" property="id"/>
<result column="language_name" property="languageName"/>
<result column="summary" property="summary"/>
<result column="is_default" property="isDefault"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="update_user" property="updateUser"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, language_name, summary, is_default, create_user, create_time, update_time, update_user, is_deleted
</sql>
</mapper>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bunny.services.mapper.UserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.bunny.dao.entity.user.AdminUser">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="nick_name" property="nickName"/>
<result column="email" property="email"/>
<result column="phone" property="phone"/>
<result column="password" property="password"/>
<result column="avatar" property="avatar"/>
<result column="sex" property="sex"/>
<result column="summary" property="summary"/>
<result column="last_login_ip" property="lastLoginIp"/>
<result column="last_login_ip_address" property="lastLoginIpAddress"/>
<result column="status" property="status"/>
<result column="create_user" property="createUser"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="update_user" property="updateUser"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
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
</sql>
</mapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB