commit b964b54fc6c3fecf4730913d0555bf10b418c321
Author: 北屋 <649947447@qq.com>
Date: Wed Feb 25 09:56:35 2026 +0800
后端基础框架
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3b41682
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..667aaef
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..8dea6c2
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/api-service/pom.xml b/api-service/pom.xml
new file mode 100644
index 0000000..1e5f3a6
--- /dev/null
+++ b/api-service/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ oailab.com
+ ai-base
+ ${revision}
+ ../pom.xml
+
+
+ api-service
+ ${revision}
+ api-service
+ api-service
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 21
+ UTF-8
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.springframework.boot
+ spring-boot-starter-batch
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 21
+ 21
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/api-service/src/main/java/oailab/com/apiservice/annotation/PassToken.java b/api-service/src/main/java/oailab/com/apiservice/annotation/PassToken.java
new file mode 100644
index 0000000..a010514
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/annotation/PassToken.java
@@ -0,0 +1,22 @@
+package oailab.com.apiservice.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * skip login validation annotation
+ *
+ * @author Benny Deng
+ * @see Benny Deng
+ * @since 6/17/24 PM11:19
+ * @version 1.0
+ **/
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PassToken {
+
+ boolean required() default true;
+
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/annotation/SearchParam.java b/api-service/src/main/java/oailab/com/apiservice/annotation/SearchParam.java
new file mode 100644
index 0000000..a152628
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/annotation/SearchParam.java
@@ -0,0 +1,31 @@
+package oailab.com.apiservice.annotation;
+
+
+import oailab.com.apiservice.enums.sys.SearchConditionEnum;
+
+import java.lang.annotation.*;
+
+/**
+ * search parameter annotation
+ *
+ * @author Benny Deng
+ * @see Benny Deng
+ * @since 6/17/24 PM11:19
+ * @version 1.0
+ **/
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface SearchParam {
+
+ /**
+ * 字段名称
+ */
+ String[] name();
+
+ /**
+ * 执行条件
+ */
+ SearchConditionEnum[] condition();
+
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/config/BatchConfig.java b/api-service/src/main/java/oailab/com/apiservice/config/BatchConfig.java
new file mode 100644
index 0000000..0614f6c
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/config/BatchConfig.java
@@ -0,0 +1,85 @@
+package oailab.com.apiservice.config;
+
+
+import jakarta.validation.constraints.NotNull;
+import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
+import org.springframework.batch.support.DatabaseType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+/**
+ * Spring Batch configuration for KingbaseES support
+ *
+ * @author Benny Deng
+ * @since 11/12/25
+ * @version 1.0
+ */
+@Configuration
+public class BatchConfig extends DefaultBatchConfiguration {
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Autowired
+ private PlatformTransactionManager transactionManager;
+
+ /**
+ * Override job repository creation to support KingbaseES
+ */
+ @Bean
+ @Override
+ public JobRepository jobRepository() {
+ JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
+ factory.setDataSource(dataSource);
+ factory.setTransactionManager(transactionManager);
+ factory.setDatabaseType(getDatabaseType());
+ factory.setIsolationLevelForCreate("ISOLATION_DEFAULT");
+ factory.setTablePrefix("BATCH_");
+ factory.setMaxVarCharLength(1000);
+ try {
+ factory.afterPropertiesSet();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ try {
+ return factory.getObject();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Get database type with KingbaseES support
+ */
+ @NotNull
+ public String getDatabaseType() {
+ try (Connection connection = dataSource.getConnection()) {
+ DatabaseMetaData metaData = connection.getMetaData();
+ String databaseProductName = metaData.getDatabaseProductName();
+
+ if (databaseProductName.toLowerCase().contains("mysql")) {
+ return DatabaseType.MYSQL.getProductName();
+ } else if (databaseProductName.toLowerCase().contains("kingbase")) {
+ // Use MySQL dialect for KingbaseES since they are compatible
+ return DatabaseType.MYSQL.getProductName();
+ } else if (databaseProductName.toLowerCase().contains("postgresql")) {
+ return DatabaseType.POSTGRES.getProductName();
+ } else {
+ // Default to MySQL
+ return DatabaseType.MYSQL.getProductName();
+ }
+ } catch (SQLException e) {
+ // Default to MySQL on error
+ return DatabaseType.MYSQL.getProductName();
+ }
+ }
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/config/ExecutorConfig.java b/api-service/src/main/java/oailab/com/apiservice/config/ExecutorConfig.java
new file mode 100644
index 0000000..0044fe3
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/config/ExecutorConfig.java
@@ -0,0 +1,21 @@
+package oailab.com.apiservice.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author Benny Deng
+ * @version 1.0
+ * @see Benny Deng
+ * @since 10/22/24
+ **/
+@Configuration
+public class ExecutorConfig {
+ @Bean
+ public ExecutorService executorService() {
+ return Executors.newFixedThreadPool(10);
+ }
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/config/FastJson2JsonRedisSerializer.java b/api-service/src/main/java/oailab/com/apiservice/config/FastJson2JsonRedisSerializer.java
new file mode 100644
index 0000000..07eb07b
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/config/FastJson2JsonRedisSerializer.java
@@ -0,0 +1,57 @@
+package oailab.com.apiservice.config;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Redis serialization by fastjson
+ *
+ * @author Benny Deng
+ * @version 1.0
+ * @see Benny Deng
+ * @since 6/17/24 PM11:19
+ **/
+public class FastJson2JsonRedisSerializer implements RedisSerializer {
+
+ public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ private final Class clazz;
+
+ public FastJson2JsonRedisSerializer(Class clazz) {
+ super();
+ this.clazz = clazz;
+ }
+
+ /**
+ * 序列化
+ */
+ @Override
+ public byte[] serialize(T t) throws SerializationException {
+ if (null == t) {
+ return new byte[0];
+ }
+
+ return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+ }
+
+ /**
+ * 反序列化
+ */
+ @Override
+ public T deserialize(byte[] bytes) throws SerializationException {
+ if (null == bytes || bytes.length == 0) {
+ return null;
+ }
+ String str = new String(bytes, DEFAULT_CHARSET);
+ try {
+ return JSON.parseObject(str, clazz);
+ } catch (Exception e) {
+ return (T) str;
+ }
+ }
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/config/MybatisPlusConfig.java b/api-service/src/main/java/oailab/com/apiservice/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..545ea2a
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/config/MybatisPlusConfig.java
@@ -0,0 +1,83 @@
+package oailab.com.apiservice.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 org.apache.ibatis.mapping.DatabaseIdProvider;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.Properties;
+
+/**
+ * MyBatis Plus configuration
+ *
+ * @author Benny Deng
+ * @see Benny Deng
+ * @since 6/17/24 PM11:19
+ * @version 1.0
+ **/
+@Configuration
+@EnableTransactionManagement
+@MapperScan(basePackages = "oailab.com.apiservice.mybatis.mapper")
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource) {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 乐观锁插件
+ interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+ // 分页插件,根据数据库类型自动适配
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(getDbType(dataSource)));
+ // 防全表更新与删除插件
+ interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
+ return interceptor;
+ }
+
+ /**
+ * 根据数据源判断数据库类型
+ * @param dataSource 数据源
+ * @return 数据库类型
+ */
+ private DbType getDbType(DataSource dataSource) {
+ try (Connection connection = dataSource.getConnection()) {
+ DatabaseMetaData metaData = connection.getMetaData();
+ String databaseProductName = metaData.getDatabaseProductName();
+
+ if (databaseProductName.toLowerCase().contains("mysql")) {
+ return DbType.MYSQL;
+ } else if (databaseProductName.toLowerCase().contains("kingbase")) {
+ return DbType.KINGBASE_ES;
+ } else {
+ // 默认使用MySQL方言
+ return DbType.MYSQL;
+ }
+ } catch (SQLException e) {
+ // 默认使用MySQL方言
+ return DbType.MYSQL;
+ }
+ }
+
+ /**
+ * 配置databaseIdProvider以支持多数据库
+ * @return DatabaseIdProvider
+ */
+ @Bean
+ public DatabaseIdProvider databaseIdProvider() {
+ VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
+ Properties properties = new Properties();
+ properties.setProperty("MySQL", "mysql");
+ properties.setProperty("KingbaseES", "kingbase");
+ databaseIdProvider.setProperties(properties);
+ return databaseIdProvider;
+ }
+}
diff --git a/api-service/src/main/java/oailab/com/apiservice/config/RedisConfig.java b/api-service/src/main/java/oailab/com/apiservice/config/RedisConfig.java
new file mode 100644
index 0000000..a72e5a7
--- /dev/null
+++ b/api-service/src/main/java/oailab/com/apiservice/config/RedisConfig.java
@@ -0,0 +1,41 @@
+package oailab.com.apiservice.config;
+
+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.StringRedisSerializer;
+
+/**
+ * Redis configuration
+ *
+ * @author Benny Deng
+ * @version 1.0
+ * @see Benny Deng
+ * @since 6/17/24 PM11:19
+ **/
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate