diff --git a/.gitignore b/.gitignore index 549e00a..ab4ff27 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ +/application-config.yml diff --git a/pom.xml b/pom.xml index 63b530a..bff9773 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,8 @@ 17 1.8.22 + ${maven.build.timestamp} + yyyy-MM-dd'T'HH:mm:ss @@ -80,7 +82,7 @@ com.alibaba druid-spring-boot-starter - 1.2.16 + 1.2.19 com.baomidou diff --git a/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt b/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt index d3741f1..5dbcf42 100644 --- a/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt +++ b/src/main/kotlin/top/fatweb/api/FatWebApiApplication.kt @@ -1,13 +1,23 @@ package top.fatweb.api +import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.transaction.annotation.EnableTransactionManagement +import java.io.File @SpringBootApplication @EnableTransactionManagement class FatWebApiApplication fun main(args: Array) { - runApplication(*args) + val logger = LoggerFactory.getLogger("main") + + if (File("application-config.yml").exists()) { + runApplication(*args) + } else { + logger.warn("File ‘application.yml’ cannot be found in the running path. The configuration file template 'application.example.yml' has been created. Please change the configuration file content and rename it to 'application.yml', and then restart the server.") + FatWebApiApplication::class.java.getResource("/application-config-template.yml")?.readText() + ?.let { File("application-config.example.yml").writeText(it) } + } } diff --git a/src/main/kotlin/top/fatweb/api/config/MybatisPlusConfig.kt b/src/main/kotlin/top/fatweb/api/config/MybatisPlusConfig.kt new file mode 100644 index 0000000..2ce739c --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/config/MybatisPlusConfig.kt @@ -0,0 +1,20 @@ +package top.fatweb.api.config + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class MybatisPlusConfig { + @Bean + fun mybatisPlusInterceptor(): MybatisPlusInterceptor { + val mybatisPlusInterceptor = MybatisPlusInterceptor() + mybatisPlusInterceptor.addInnerInterceptor(OptimisticLockerInnerInterceptor()) + mybatisPlusInterceptor.addInnerInterceptor(PaginationInnerInterceptor()) + + return mybatisPlusInterceptor + } + +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/config/RedisConfig.kt b/src/main/kotlin/top/fatweb/api/config/RedisConfig.kt new file mode 100644 index 0000000..fd13a65 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/config/RedisConfig.kt @@ -0,0 +1,34 @@ +package top.fatweb.api.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.data.redis.connection.RedisConnectionFactory +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer +import org.springframework.data.redis.serializer.StringRedisSerializer + +@Configuration +class RedisConfig { + @Bean + fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<*, *> { + val redisTemplate = RedisTemplate() + redisTemplate.connectionFactory = redisConnectionFactory + val stringRedisSerializer = StringRedisSerializer() + val objectMapper = ObjectMapper().registerModules(JavaTimeModule()) + val anyJackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(objectMapper, Any::class.java) + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + redisTemplate.keySerializer = stringRedisSerializer + redisTemplate.valueSerializer = anyJackson2JsonRedisSerializer + + // Hash的key也采用StringRedisSerializer的序列化方式 + redisTemplate.hashKeySerializer = stringRedisSerializer + redisTemplate.hashValueSerializer = anyJackson2JsonRedisSerializer + + redisTemplate.afterPropertiesSet() + + return redisTemplate + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/config/SwaggerConfig.kt b/src/main/kotlin/top/fatweb/api/config/SwaggerConfig.kt new file mode 100644 index 0000000..6ce83a6 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/config/SwaggerConfig.kt @@ -0,0 +1,19 @@ +package top.fatweb.api.config + +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Contact +import io.swagger.v3.oas.models.info.Info +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import top.fatweb.api.constants.ServerConstants + +@Configuration +class SwaggerConfig { + + @Bean + fun customOpenAPI(): OpenAPI? { + val contact = Contact().name("FatttSnake").url("https://fatweb.top").email("fatttsnake@fatweb.top") + return OpenAPI().info(Info().title("FatWeb API 文档").description("FatWeb 后端 API 文档,包含各个 Controller 调用信息").contact(contact).version( + ServerConstants.version)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/constants/SecurityConstants.kt b/src/main/kotlin/top/fatweb/api/constants/SecurityConstants.kt new file mode 100644 index 0000000..e92897a --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/constants/SecurityConstants.kt @@ -0,0 +1,29 @@ +package top.fatweb.api.constants + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component +import java.security.MessageDigest +import java.util.concurrent.TimeUnit + +@Component +@ConfigurationProperties("app.security") +object SecurityConstants { + var headerString = "Authorization" + + var tokenPrefix = "Bearer " + + var jwtTtl = 2L + + var jwtTtlUnit = TimeUnit.HOURS + + lateinit var jwtKey: String + + private fun ByteArray.hex(): String { + return joinToString("") { "%02X".format(it) } + } + + private fun String.md5(): String { + val bytes = MessageDigest.getInstance("MD5").digest(this.toByteArray()) + return bytes.hex() + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/constants/ServerConstants.kt b/src/main/kotlin/top/fatweb/api/constants/ServerConstants.kt new file mode 100644 index 0000000..adabb4e --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/constants/ServerConstants.kt @@ -0,0 +1,17 @@ +package top.fatweb.api.constants + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.stereotype.Component +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime + +@Component +@ConfigurationProperties("app") +object ServerConstants { + lateinit var version: String + + lateinit var buildTime: String + + fun buildZoneDateTime(zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime = LocalDateTime.parse(buildTime).atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId) +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/filter/ExceptionFilter.kt b/src/main/kotlin/top/fatweb/api/filter/ExceptionFilter.kt new file mode 100644 index 0000000..4fcfbc2 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/filter/ExceptionFilter.kt @@ -0,0 +1,15 @@ +package top.fatweb.api.filter + +import jakarta.servlet.* +import java.lang.Exception + +class ExceptionFilter : Filter { + override fun doFilter(servletRequest: ServletRequest?, servletResponse: ServletResponse?, filterChain: FilterChain?) { + try { + filterChain!!.doFilter(servletRequest, servletResponse) + } catch (e: Exception) { + servletRequest?.setAttribute("filter.error", e) + servletRequest?.getRequestDispatcher("/error/thrown")?.forward(servletRequest, servletResponse) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/filter/JwtAuthenticationTokenFilter.kt b/src/main/kotlin/top/fatweb/api/filter/JwtAuthenticationTokenFilter.kt new file mode 100644 index 0000000..9319248 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/filter/JwtAuthenticationTokenFilter.kt @@ -0,0 +1,26 @@ +package top.fatweb.api.filter + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.util.StringUtils +import org.springframework.web.filter.OncePerRequestFilter +import top.fatweb.api.constants.SecurityConstants +import top.fatweb.api.utils.RedisUtil + +class JwtAuthenticationTokenFilter(private val redisUtil: RedisUtil) : OncePerRequestFilter() { + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val token = request.getHeader(SecurityConstants.headerString) + + if (!StringUtils.hasText(token) || "/error/thrown" == request.servletPath) { + filterChain.doFilter(request, response) + return + } + + + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/utils/JwtUtil.kt b/src/main/kotlin/top/fatweb/api/utils/JwtUtil.kt new file mode 100644 index 0000000..9d18414 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/utils/JwtUtil.kt @@ -0,0 +1,5 @@ +package top.fatweb.api.utils + +class JwtUtil { + +} \ No newline at end of file diff --git a/src/main/resources/application-config-template.yml b/src/main/resources/application-config-template.yml new file mode 100644 index 0000000..88a4403 --- /dev/null +++ b/src/main/resources/application-config-template.yml @@ -0,0 +1,32 @@ +server: +# port: 8080 # Server port + +spring: + datasource: + url: jdbc:mysql://localhost # MySQL url + username: root # MySQL username + password: root # MySQL password + + druid: +# initial-size: 20 # Initial number of connections +# min-idle: 20 # Minimum number of connection pools +# max-active: 20 # Maximum number of connection pools +# max-wait: 3000 # Maximum connection timeout +# min-evictable-idle-time-millis: 300000 # Minimum time for a connection to live in the pool +# max-evictable-idle-time-millis: 900000 # Maximum time for a connection to live in the pool +# break-after-acquire-failure: true # Terminate the retry when the server fails to reconnect for the specified number of times +# connection-error-retry-attempts: 5 # Number of failed reconnections + + data: + redis: +# database: 0 # Redis database (default: 0) +# host: localhost # Redis host (default: localhost) +# port: 6379 # Redis port (default: 6379) +# password: # Password of redis +# connect-timeout: 3000 # Redis connect timeout +# lettuce: +# pool: +# min-idle: 0 +# max-idle: 8 +# max-active: 8 +# max-wait: -1ms \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8b13789..27104c4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1 +1,15 @@ +app: + version: @project.version@ + build-time: @build.timestamp@ + security: + header-string: "Authorization" + token-prefix: "Bearer " + jwt-ttl: 2 + jwt-ttl-unit: hours +spring: + profiles: + active: config + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt b/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt index a4324ff..34fc696 100644 --- a/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt +++ b/src/test/kotlin/top/fatweb/api/FatWebApiApplicationTests.kt @@ -2,12 +2,16 @@ package top.fatweb.api import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest +import top.fatweb.api.constants.SecurityConstants +import java.security.MessageDigest +import java.util.* @SpringBootTest class FatWebApiApplicationTests { @Test fun contextLoads() { + SecurityConstants.jwtKey } }