diff --git a/code/container-ocr/ocr-springboot/.gitignore b/code/container-ocr/ocr-springboot/.gitignore
new file mode 100644
index 00000000..480bdf52
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/.gitignore
@@ -0,0 +1,39 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+.kotlin
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/.idea/.gitignore b/code/container-ocr/ocr-springboot/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/code/container-ocr/ocr-springboot/.idea/encodings.xml b/code/container-ocr/ocr-springboot/.idea/encodings.xml
new file mode 100644
index 00000000..aa00ffab
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/.idea/misc.xml b/code/container-ocr/ocr-springboot/.idea/misc.xml
new file mode 100644
index 00000000..132404bc
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/.idea/vcs.xml b/code/container-ocr/ocr-springboot/.idea/vcs.xml
new file mode 100644
index 00000000..c2365ab1
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/pom.xml b/code/container-ocr/ocr-springboot/pom.xml
new file mode 100644
index 00000000..729d16f1
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/pom.xml
@@ -0,0 +1,92 @@
+
+
+ 4.0.0
+
+ cn.g3soft
+ ocr-springboot
+ 1.0-SNAPSHOT
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.18
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.23
+
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ 6.2.2.jre8
+
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.1
+
+
+
+ com.alibaba
+ easyexcel
+ 4.0.3
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.14.0
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.32
+ provided
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ alipay-releases
+ Alipay Releases
+ https://mvn.cloud.alipay.com/nexus/content/repositories/releases/
+
+
+
+
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java
new file mode 100644
index 00000000..f606f4a2
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java
@@ -0,0 +1,24 @@
+package cn.g3soft;
+
+import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions;
+import cn.g3soft.utils.uniqueId.idgen.UniqueId;
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+
+@SpringBootApplication(exclude = {
+ DataSourceAutoConfiguration.class,
+ DruidDataSourceAutoConfigure.class ,
+})
+public class ApiApplication {
+ public static void main(String[] args){
+ // 初始化雪花id
+ IdGeneratorOptions options = new IdGeneratorOptions();
+ // 每个项目中都需要设置这个,并且保证 <任意服务器> 下的id不重复
+ options.WorkerId = 1;
+ UniqueId.setIdGenerator(options);
+
+ SpringApplication.run(ApiApplication.class, args);
+ }
+}
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/JdbcConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/JdbcConfig.java
new file mode 100644
index 00000000..b60062c2
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/JdbcConfig.java
@@ -0,0 +1,19 @@
+package cn.g3soft.config;
+
+import cn.g3soft.config.datasource.DataSourceManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+@Configuration
+public class JdbcConfig {
+ @Bean
+ public DataSourceManager dataSourceManager() {
+ return new DataSourceManager();
+ }
+
+ @Bean
+ public JdbcTemplate jdbcTemplate(DataSourceManager dataSourceManager) {
+ return new JdbcTemplate(dataSourceManager);
+ }
+}
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebCorsConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebCorsConfig.java
new file mode 100644
index 00000000..26af64ce
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebCorsConfig.java
@@ -0,0 +1,33 @@
+package cn.g3soft.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+@Configuration
+public class WebCorsConfig {
+ @Bean
+ public CorsFilter corsFilter() {
+ // 1.创建 CORS 配置对象
+ CorsConfiguration config = new CorsConfiguration();
+ // 支持域
+ config.addAllowedOriginPattern("*");
+ // 是否发送 Cookie
+ config.setAllowCredentials(true);
+ // 支持请求方式
+ config.addAllowedMethod("*");
+ // 允许的原始请求头部信息
+ config.addAllowedHeader("*");
+ // 暴露的头部信息
+ config.addExposedHeader("*");
+ // 有效期 1800秒,设置预检请求(OPTIONS请求)的缓存时间,1800秒意味着客户端可以缓存这个设置1800秒
+ config.setMaxAge(1800L);
+ // 2.添加地址映射
+ UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
+ corsConfigurationSource.registerCorsConfiguration("/**", config);
+ // 3.返回 CorsFilter 对象
+ return new CorsFilter(corsConfigurationSource);
+ }
+}
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java
new file mode 100644
index 00000000..47395892
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java
@@ -0,0 +1,27 @@
+package cn.g3soft.config;
+
+import cn.g3soft.interceptor.AuthInterceptor;
+import cn.g3soft.interceptor.DataSourceInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(new AuthInterceptor())
+ .addPathPatterns("/**")
+ .excludePathPatterns(
+ "/auth/login",
+ "/error"
+ );
+
+ registry.addInterceptor(new DataSourceInterceptor())
+ .addPathPatterns("/**")
+ .excludePathPatterns(
+ "/auth/login",
+ "/error"
+ );
+ }
+}
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DataSourceManager.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DataSourceManager.java
new file mode 100644
index 00000000..51a8dea1
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DataSourceManager.java
@@ -0,0 +1,106 @@
+package cn.g3soft.config.datasource;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import cn.g3soft.context.DataSourceContextHolder;
+import cn.g3soft.utils.SpringUtils;
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DataSourceManager extends AbstractRoutingDataSource {
+ private static final String CONFIG_PATH = "dbconfigs/";
+ private static final Map dataSources = new ConcurrentHashMap<>();
+
+ public DataSourceManager() {
+ setTargetDataSources(new ConcurrentHashMap<>(dataSources));
+ afterPropertiesSet();
+ }
+
+ @Override
+ protected Object determineCurrentLookupKey() {
+ return DataSourceContextHolder.getDataSourceKey();
+ }
+
+ public static Connection getConnection(String orgCode) throws Exception {
+ DataSource dataSource = getOrCreateDataSource(orgCode);
+
+ return dataSource.getConnection();
+ }
+
+ /**
+ * 获取或创建数据源
+ */
+ public static DataSource getOrCreateDataSource(String orgCode) throws Exception {
+
+ DataSource ds = dataSources.get(orgCode);
+ if (ds != null) {
+ return ds;
+ }
+
+ synchronized (DataSourceManager.class) {
+
+ ds = dataSources.get(orgCode);
+ if (ds != null) {
+ return ds;
+ }
+
+ ds = createDataSource(orgCode);
+ dataSources.put(orgCode, ds);
+
+ refresh();
+
+ return ds;
+ }
+ }
+
+ /**
+ * 刷新 routing datasource
+ */
+ private static void refresh() {
+
+ DataSourceManager routing = SpringUtils.getBean(DataSourceManager.class);
+ routing.setTargetDataSources(new ConcurrentHashMap<>(dataSources));
+ routing.afterPropertiesSet();
+ }
+
+ /**
+ * 创建数据源
+ */
+ private static DataSource createDataSource(String orgCode) throws Exception {
+
+ String file = CONFIG_PATH + orgCode + ".properties";
+
+ try (InputStream in = DataSourceManager.class
+ .getClassLoader()
+ .getResourceAsStream(file)) {
+
+ if (in == null) {
+ throw new RuntimeException("未找到配置文件: " + file);
+ }
+
+ Properties p = new Properties();
+ p.load(in);
+
+ DruidDataSource ds = new DruidDataSource();
+
+ ds.setUrl(p.getProperty("url"));
+ ds.setUsername(p.getProperty("username"));
+ ds.setPassword(p.getProperty("password"));
+ ds.setDriverClassName(p.getProperty("driver"));
+
+ ds.setInitialSize(2);
+ ds.setMaxActive(20);
+ ds.setMinIdle(2);
+ ds.setValidationQuery("SELECT 1");
+
+ ds.init();
+
+ return ds;
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DruidConfig.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DruidConfig.java
new file mode 100644
index 00000000..e8b3cba7
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DruidConfig.java
@@ -0,0 +1,70 @@
+package cn.g3soft.config.datasource;
+
+import com.alibaba.druid.support.http.StatViewServlet;
+import com.alibaba.druid.support.http.WebStatFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class DruidConfig {
+
+ @Bean
+ public ServletRegistrationBean statViewServlet() {
+ // 监控信息显示页面
+ StatViewServlet statViewServlet = new StatViewServlet();
+ // 访问监控信息显示页面的 url 路径(建议值为 /druid/* )
+ String urlPattern = "/druid/*";
+
+ ServletRegistrationBean bean = new ServletRegistrationBean<>(statViewServlet, urlPattern);
+
+ Map initParams = new HashMap<>();
+ // 是否允许清空统计数据
+ initParams.put("resetEnable", "false");
+ // 登录监控信息显示页面的用户名
+ initParams.put("loginUsername", "admin");
+ // 登录监控信息显示页面的密码
+ initParams.put("loginPassword", "admin");
+ // 允许访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
+ initParams.put("allow", "127.0.0.1");
+ // 拒绝访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
+// initParams.put("deny", "");
+
+ bean.setInitParameters(initParams);
+ return bean;
+ }
+
+ @Bean
+ public FilterRegistrationBean webStatFilter() {
+ FilterRegistrationBean bean = new FilterRegistrationBean<>();
+ // 网络监控过滤器(用于采集 web-jdbc 关联监控的数据)
+ bean.setFilter(new WebStatFilter());
+ // 过滤所有的 url 路径
+ Collection urlPatterns = new ArrayList<>();
+ urlPatterns.add("/*");
+ bean.setUrlPatterns(urlPatterns);
+
+ Map initParams = new HashMap<>();
+ // 排除不必要采集的 url 路径,以逗号“,”分割
+ initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.map,/druid/*");
+ // 是否使用 session 监控功能
+ initParams.put("sessionStatEnable", "true");
+ // 是否使用 session 监控最大数量(默认是1000)
+ initParams.put("sessionStatMaxCount", "1000");
+ // 使得 druid 能够知道当前的 session 的用户是谁,根据需要,把改值修改为你 user 信息保存在 session 中的 sessionName
+ initParams.put("principalSessionName", "session_user_key");
+ // 如果你的 user 信息保存在 cookie 中,你可以配置 principalCookieName,使得 druid 知道当前的 user 是谁,根据需要,把该值修改为你 user 信息保存在 cookie 中的 cookieName
+ initParams.put("principalCookieName", "cookie_user_key");
+ // 是否监控单个 url 调用的 sql 列表
+ initParams.put("profileEnable", "true");
+ bean.setInitParameters(initParams);
+ return bean;
+ }
+
+}
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/DataSourceContextHolder.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/DataSourceContextHolder.java
new file mode 100644
index 00000000..7dc19669
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/DataSourceContextHolder.java
@@ -0,0 +1,17 @@
+package cn.g3soft.context;
+
+public class DataSourceContextHolder {
+ private static final ThreadLocal contextHolder = new ThreadLocal<>();
+
+ public static void setDataSourceKey(String dataSourceKey) {
+ contextHolder.set(dataSourceKey);
+ }
+
+ public static String getDataSourceKey() {
+ return contextHolder.get();
+ }
+
+ public static void clearDataSourceKey() {
+ contextHolder.remove();
+ }
+}
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/UserContext.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/UserContext.java
new file mode 100644
index 00000000..61e939f3
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/UserContext.java
@@ -0,0 +1,30 @@
+package cn.g3soft.context;
+
+import lombok.Getter;
+
+@Getter
+public class UserContext {
+ private final String userId;
+ private final String orgId;
+
+ // 使用 ThreadLocal 保存当前线程的用户上下文
+ private static final ThreadLocal userContextThreadLocal = new ThreadLocal<>();
+
+ // 私有构造,防止外部直接 new
+ private UserContext(String userId, String orgId) {
+ this.userId = userId;
+ this.orgId = orgId;
+ }
+
+ public static void set(String userId, String orgId) {
+ userContextThreadLocal.set(new UserContext(userId, orgId));
+ }
+
+ public static UserContext get() {
+ return userContextThreadLocal.get();
+ }
+
+ public static void remove() {
+ userContextThreadLocal.remove();
+ }
+}
\ No newline at end of file
diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/AuthController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/AuthController.java
new file mode 100644
index 00000000..39e54d9b
--- /dev/null
+++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/AuthController.java
@@ -0,0 +1,93 @@
+package cn.g3soft.controller;
+
+import cn.g3soft.config.datasource.DataSourceManager;
+import cn.g3soft.context.DataSourceContextHolder;
+import cn.g3soft.utils.DbUtils;
+import cn.g3soft.utils.Result;
+import cn.g3soft.utils.jwt.JwtPayload;
+import cn.g3soft.utils.jwt.JwtUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+ @Resource
+ private DbUtils dbUtils;
+
+ /**
+ * 用户登录接口
+ *
+ * @param params 参数集合,包含:
+ * - orgid: 机构码(必填)
+ * - userid: 用户账号(必填)
+ * - password: 登录密码
+ * @return 登录结果(Map 类型),包含:
+ * - token: 登录令牌
+ * - username: 用户名称
+ */
+ @PostMapping("/login")
+ public Result