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