This commit is contained in:
Ilia Ravin 2022-11-10 17:21:26 +03:00 committed by GitHub
commit f60027aac3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 245 additions and 1 deletions

View File

@ -0,0 +1,9 @@
package ru.touchin.common.spring.annotations
import org.springframework.context.annotation.Import
import ru.touchin.common.spring.processors.RequiredByBeanDefinitionPostProcessor
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS)
@Import(RequiredByBeanDefinitionPostProcessor::class)
annotation class RequiredBy(vararg val value: String)

View File

@ -0,0 +1,38 @@
package ru.touchin.common.spring.processors
import org.springframework.beans.BeansException
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
import org.springframework.stereotype.Component
import ru.touchin.common.spring.annotations.RequiredBy
@Component
class RequiredByBeanDefinitionPostProcessor : BeanDefinitionRegistryPostProcessor {
@Throws(BeansException::class)
override fun postProcessBeanDefinitionRegistry(registry: BeanDefinitionRegistry) {
for (beanName in registry.beanDefinitionNames) {
val beanClassName = registry.getBeanDefinition(beanName).beanClassName?:continue
getDependantBeanNames(beanClassName).forEach { dependantBeanName ->
registry.getBeanDefinition(dependantBeanName).setDependsOn(beanName)
}
}
}
@Throws(BeansException::class)
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
}
private fun getDependantBeanNames(beanClassName: String): List<String> {
val beanClass = Class.forName(beanClassName)
var dependantBeans = emptyList<String>()
if (beanClass.isAnnotationPresent(RequiredBy::class.java)) {
dependantBeans = beanClass.getAnnotation(RequiredBy::class.java).value.toList()
}
return dependantBeans
}
}

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -60,3 +60,4 @@ include("s3-storage")
include("server-info-spring-web")
include("geoip-core")
include("user-agent")
include("smart-migration")

View File

@ -0,0 +1,12 @@
plugins {
id("kotlin")
id("kotlin-spring")
id("maven-publish")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation project(":common-spring")
}

View File

@ -0,0 +1,59 @@
@file:Suppress("unused")
package ru.touchin.smartmigration
import org.springframework.stereotype.Component
import ru.touchin.common.spring.annotations.RequiredBy
import ru.touchin.smartmigration.logic.DataSourceSQL
import ru.touchin.smartmigration.logic.factory.DataSourceSqlFactoryImpl
import java.sql.Date
import java.text.SimpleDateFormat
import javax.annotation.PostConstruct
import javax.sql.DataSource
private val SQL_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
private val CURRENT_TIME_SQL: String
get() = SQL_DATE_FORMAT.format(Date(System.currentTimeMillis()))
@Component
@RequiredBy("liquibase")
class BeforeLiquibase(
private val dataSource: DataSource
) {
val dataSourceSql: DataSourceSQL = DataSourceSqlFactoryImpl()
.getDataSourceSql(dataSource.connection.metaData.databaseProductName)
@PostConstruct
fun doAction() {
val buildNumber = System.getenv("BUILD_NUMBER")
?: return
checkMigrationTable()
if (checkBuildMigrationExecuted(buildNumber)) {
System.setProperty("spring.liquibase.enabled", "false")
} else {
insertMigration(buildNumber)
}
}
private fun checkBuildMigrationExecuted(buildNumber: String): Boolean {
return dataSourceSql.getMigrationCheckSQL(buildNumber).let {
dataSource.connection.createStatement().executeQuery(it).next()
}
}
private fun checkMigrationTable() {
dataSourceSql.getTableCheckSQL().let {
dataSource.connection.createStatement().execute(it)
}
}
private fun insertMigration(buildNumber: String) {
dataSourceSql.getInsertMigrationSQL(buildNumber, CURRENT_TIME_SQL).let {
dataSource.connection.createStatement().execute(it)
}
}
}

View File

@ -0,0 +1,8 @@
package ru.touchin.smartmigration
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
@Configuration
@ComponentScan("ru.touchin.smartmigration")
open class SmartMigrationConfig

View File

@ -0,0 +1,7 @@
package ru.touchin.smartmigration.annotation
import org.springframework.context.annotation.Import
import ru.touchin.smartmigration.SmartMigrationConfig
@Import(SmartMigrationConfig::class)
annotation class EnableSmartMigration

View File

@ -0,0 +1,11 @@
package ru.touchin.smartmigration.logic
interface DataSourceSQL {
fun getTableCheckSQL(): String
fun getMigrationCheckSQL(buildNumber: String): String
fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String
}

View File

@ -0,0 +1,33 @@
package ru.touchin.smartmigration.logic
import org.intellij.lang.annotations.Language
private const val MIGRATION_TABLE_NAME = "SMART_MIGRATION"
class PostgresDataSourceImpl: DataSourceSQL {
@Language("SQL")
override fun getTableCheckSQL(): String {
return """
CREATE TABLE IF NOT EXISTS smart_migration (
ID BIGSERIAL PRIMARY KEY,
BUILD_NUMBER VARCHAR(255) NOT NULL,
DATE timestamp NOT NULL
);
"""
}
@Language("SQL")
override fun getMigrationCheckSQL(buildNumber: String): String {
return "SELECT * FROM $MIGRATION_TABLE_NAME WHERE BUILD_NUMBER = '$buildNumber'"
}
@Language("SQL")
override fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String {
return """
INSERT INTO $MIGRATION_TABLE_NAME (BUILD_NUMBER, DATE)
VALUES ('$buildNumber', '$formattedTime');
"""
}
}

View File

@ -0,0 +1,38 @@
package ru.touchin.smartmigration.logic
import org.intellij.lang.annotations.Language
private const val MIGRATION_TABLE_NAME = "SMART_MIGRATION"
class SqlDatasourceImpl:DataSourceSQL {
@Language("TSQL")
override fun getTableCheckSQL(): String {
return """
IF NOT EXISTS (
SELECT * FROM sysobjects WHERE name = '$MIGRATION_TABLE_NAME' and xtype='U'
)
CREATE TABLE SMART_MIGRATION (
ID BIGINT PRIMARY KEY IDENTITY ,
BUILD_NUMBER VARCHAR(255) NOT NULL,
DATE DATETIME NOT NULL
);
"""
}
@Language("TSQL")
override fun getMigrationCheckSQL(buildNumber: String): String {
return "SELECT * FROM $MIGRATION_TABLE_NAME WHERE BUILD_NUMBER = '$buildNumber'"
}
@Language("TSQL")
override fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String {
return """
INSERT INTO $MIGRATION_TABLE_NAME (BUILD_NUMBER, DATE)
VALUES ('$buildNumber', '$formattedTime');
"""
}
}

View File

@ -0,0 +1,9 @@
package ru.touchin.smartmigration.logic.factory
import ru.touchin.smartmigration.logic.DataSourceSQL
interface DataSourceSqlFactory {
fun getDataSourceSql(driverName: String): DataSourceSQL
}

View File

@ -0,0 +1,19 @@
package ru.touchin.smartmigration.logic.factory
import ru.touchin.smartmigration.logic.DataSourceSQL
import ru.touchin.smartmigration.logic.PostgresDataSourceImpl
import ru.touchin.smartmigration.logic.SqlDatasourceImpl
class DataSourceSqlFactoryImpl: DataSourceSqlFactory {
override fun getDataSourceSql(driverName: String): DataSourceSQL {
return when(driverName){
"Microsoft SQL Server" -> SqlDatasourceImpl()
"PostgresSQL" -> PostgresDataSourceImpl()
else -> {
PostgresDataSourceImpl()
}
}
}
}