From 56f5ba4e6b7d4fc9508d2b537b9c935863fedf38 Mon Sep 17 00:00:00 2001 From: oneao Date: Sun, 10 May 2026 21:54:13 +0800 Subject: [PATCH] 20260510215412 --- .../ocr-springboot/.idea/misc.xml | 2 +- code/container-ocr/ocr-springboot/pom.xml | 3 +- .../config/datasource/DataSourceManager.java | 134 +++++++++++------- .../g3soft/config/datasource/DruidConfig.java | 81 ++++++----- .../src/main/resources/application-build.yml | 7 + .../src/main/resources/application-dev.yml | 7 + .../src/main/resources/application.yml | 11 +- .../main/resources/dbconfigs/g3hd.properties | 14 +- code/container-ocr/ocr-vue/.env | 4 +- code/container-ocr/ocr-vue/components.d.ts | 8 -- .../ocr-vue/src/views/auth/login.vue | 3 - .../ocr-vue/src/views/container/index.vue | 1 + 12 files changed, 167 insertions(+), 108 deletions(-) create mode 100644 code/container-ocr/ocr-springboot/src/main/resources/application-build.yml create mode 100644 code/container-ocr/ocr-springboot/src/main/resources/application-dev.yml diff --git a/code/container-ocr/ocr-springboot/.idea/misc.xml b/code/container-ocr/ocr-springboot/.idea/misc.xml index 132404bc..4b661a5f 100644 --- a/code/container-ocr/ocr-springboot/.idea/misc.xml +++ b/code/container-ocr/ocr-springboot/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/pom.xml b/code/container-ocr/ocr-springboot/pom.xml index c2b0e033..9657209f 100644 --- a/code/container-ocr/ocr-springboot/pom.xml +++ b/code/container-ocr/ocr-springboot/pom.xml @@ -38,10 +38,11 @@ 1.2.23 + com.microsoft.sqlserver mssql-jdbc - 6.2.2.jre8 + 13.4.0.jre8 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 index aa9eaae6..3ff217a2 100644 --- 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 @@ -1,24 +1,33 @@ package cn.g3soft.config.datasource; -import com.alibaba.druid.pool.DruidDataSource; import cn.g3soft.context.DataSourceContextHolder; -import cn.g3soft.utils.SpringUtils; +import com.alibaba.druid.pool.DruidDataSource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +@Slf4j public class DataSourceManager extends AbstractRoutingDataSource { private static final String CONFIG_PATH = "dbconfigs/"; + private static final Pattern ORG_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_-]+$"); + private static final String VALIDATION_QUERY = "SELECT 1"; + private static volatile DataSourceManager routingDataSource; + private static final Map dataSources = new ConcurrentHashMap<>(); public DataSourceManager() { - setTargetDataSources(new ConcurrentHashMap<>(dataSources)); + routingDataSource = this; + setTargetDataSources(toTargetDataSources()); afterPropertiesSet(); } @@ -27,22 +36,13 @@ public class DataSourceManager extends AbstractRoutingDataSource { return DataSourceContextHolder.getDataSourceKey(); } - /** - * 获取连接(带异常保护) - */ public static Connection getConnection(String orgCode) throws Exception { - try { - DataSource dataSource = getOrCreateDataSource(orgCode); - return dataSource.getConnection(); - } catch (Exception e) { - throw new RuntimeException("获取数据库连接失败: " + orgCode, e); - } + DataSource ds = getOrCreateDataSource(orgCode); + return ds.getConnection(); } - /** - * 获取或创建数据源 - */ public static DataSource getOrCreateDataSource(String orgCode) throws Exception { + validateOrgCode(orgCode); DataSource ds = dataSources.get(orgCode); if (ds != null) { @@ -50,47 +50,37 @@ public class DataSourceManager extends AbstractRoutingDataSource { } synchronized (DataSourceManager.class) { - ds = dataSources.get(orgCode); if (ds != null) { return ds; } - // 创建数据源 ds = createDataSource(orgCode); - // ===== 关键:先验证连接,防止缓存坏数据源 ===== try (Connection conn = ds.getConnection()) { - // 成功才缓存 dataSources.put(orgCode, ds); } catch (Exception e) { - if (ds instanceof DruidDataSource) { - ((DruidDataSource) ds).close(); - } + closeQuietly(ds); throw new RuntimeException("数据库连接失败: " + orgCode, e); } - // 刷新 routing datasource refresh(); - + log.info("Loaded datasource for orgCode: {}", orgCode); return ds; } } - /** - * 刷新 routing datasource - */ private static void refresh() { - DataSourceManager routing = SpringUtils.getBean(DataSourceManager.class); - routing.setTargetDataSources(new ConcurrentHashMap<>(dataSources)); + DataSourceManager routing = routingDataSource; + if (routing == null) { + return; + } + + routing.setTargetDataSources(toTargetDataSources()); routing.afterPropertiesSet(); } - /** - * 创建数据源(已优化:防止无限重连/卡死) - */ private static DataSource createDataSource(String orgCode) throws Exception { - String file = CONFIG_PATH + orgCode + ".properties"; try (InputStream in = DataSourceManager.class @@ -98,39 +88,87 @@ public class DataSourceManager extends AbstractRoutingDataSource { .getResourceAsStream(file)) { if (in == null) { - throw new RuntimeException("未找到配置文件: " + file); + throw new RuntimeException("未找到数据源配置文件: " + file); } Properties p = new Properties(); p.load(in); + requireProperty(p, "url", file); + requireProperty(p, "username", file); + requireProperty(p, "password", file); + requireProperty(p, "driver", file); DruidDataSource ds = new DruidDataSource(); + ds.setName(orgCode); ds.setUrl(p.getProperty("url")); ds.setUsername(p.getProperty("username")); ds.setPassword(p.getProperty("password")); ds.setDriverClassName(p.getProperty("driver")); - // ===== 基础连接池配置 ===== ds.setInitialSize(1); - ds.setMaxActive(10); ds.setMinIdle(1); + ds.setMaxActive(10); + ds.setMaxWait(3000); - // ===== 核心:防止卡死 / 无限重试 ===== - ds.setMaxWait(3000); // 获取连接最大等待3秒 - ds.setBreakAfterAcquireFailure(true); // 获取失败直接抛异常 - ds.setConnectionErrorRetryAttempts(0); // 不重试 - ds.setFailFast(true); // 初始化时检测连接 + ds.setBreakAfterAcquireFailure(true); + ds.setConnectionErrorRetryAttempts(0); + ds.setFailFast(true); - // ===== 连接校验 ===== - ds.setValidationQuery("SELECT 1"); - ds.setTestOnBorrow(true); - ds.setTestWhileIdle(false); + ds.setValidationQuery(VALIDATION_QUERY); + + ds.setTestOnBorrow(false); + ds.setTestOnReturn(false); + ds.setTestWhileIdle(true); + + ds.setTimeBetweenEvictionRunsMillis(60000); + ds.setMinEvictableIdleTimeMillis(300000); + + ds.setKeepAlive(false); + + ds.setPoolPreparedStatements(false); + + ds.setFilters("stat,slf4j"); + ds.setUseGlobalDataSourceStat(true); + ds.setConnectionProperties( + "druid.stat.mergeSql=true;" + + "druid.stat.slowSqlMillis=3000;" + + "druid.stat.logSlowSql=true" + ); + + ds.setRemoveAbandoned(false); - // 初始化(此时若连接失败会快速抛异常) ds.init(); - return ds; } } -} \ No newline at end of file + + private static Map toTargetDataSources() { + return new HashMap<>(dataSources); + } + + private static void validateOrgCode(String orgCode) { + if (StringUtils.isBlank(orgCode)) { + throw new IllegalArgumentException("机构码不能为空"); + } + if (!ORG_CODE_PATTERN.matcher(orgCode).matches()) { + throw new IllegalArgumentException("机构码只能包含字母、数字、下划线和中划线: " + orgCode); + } + } + + private static void requireProperty(Properties properties, String key, String file) { + if (StringUtils.isBlank(properties.getProperty(key))) { + throw new IllegalArgumentException("数据源配置缺少必要项 " + key + ": " + file); + } + } + + private static void closeQuietly(DataSource dataSource) { + if (dataSource instanceof DruidDataSource) { + try { + ((DruidDataSource) dataSource).close(); + } catch (Exception e) { + log.warn("Close datasource failed", e); + } + } + } +} 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 index e8b3cba7..d2b25c12 100644 --- 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 @@ -7,64 +7,77 @@ 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.Collections; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { + // ===================== 固定配置 ===================== + + private static final boolean ENABLED = true; + + private static final String URL_PATTERN = "/druid/*"; + + private static final String LOGIN_USERNAME = "admin"; + + private static final String LOGIN_PASSWORD = "admin"; + + // 只允许本机访问(生产环境建议改内网IP) + private static final String ALLOW = "127.0.0.1"; + + private static final String DENY = ""; + + private static final boolean RESET_ENABLE = false; + + private static final String EXCLUSIONS = + "*.js,*.gif,*.jpg,*.jpeg,*.png,*.css,*.ico,*.map,/druid/*"; + + // =================================================== + @Bean public ServletRegistrationBean statViewServlet() { - // 监控信息显示页面 - StatViewServlet statViewServlet = new StatViewServlet(); - // 访问监控信息显示页面的 url 路径(建议值为 /druid/* ) - String urlPattern = "/druid/*"; - ServletRegistrationBean bean = new ServletRegistrationBean<>(statViewServlet, urlPattern); + StatViewServlet servlet = new StatViewServlet(); + ServletRegistrationBean bean = + new ServletRegistrationBean<>(servlet, URL_PATTERN); 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", ""); + initParams.put("resetEnable", String.valueOf(RESET_ENABLE)); + initParams.put("loginUsername", LOGIN_USERNAME); + initParams.put("loginPassword", LOGIN_PASSWORD); + initParams.put("allow", ALLOW); + + if (DENY != null && !DENY.isEmpty()) { + initParams.put("deny", DENY); + } bean.setInitParameters(initParams); + bean.setEnabled(ENABLED); + return bean; } @Bean public FilterRegistrationBean webStatFilter() { - FilterRegistrationBean bean = new FilterRegistrationBean<>(); - // 网络监控过滤器(用于采集 web-jdbc 关联监控的数据) + + FilterRegistrationBean bean = + new FilterRegistrationBean<>(); + bean.setFilter(new WebStatFilter()); - // 过滤所有的 url 路径 - Collection urlPatterns = new ArrayList<>(); - urlPatterns.add("/*"); - bean.setUrlPatterns(urlPatterns); + bean.setUrlPatterns(Collections.singletonList("/*")); + bean.setOrder(2); Map initParams = new HashMap<>(); - // 排除不必要采集的 url 路径,以逗号“,”分割 - initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.map,/druid/*"); - // 是否使用 session 监控功能 + initParams.put("exclusions", EXCLUSIONS); 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); + bean.setEnabled(ENABLED); + return bean; } - -} +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/resources/application-build.yml b/code/container-ocr/ocr-springboot/src/main/resources/application-build.yml new file mode 100644 index 00000000..4b70430f --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/resources/application-build.yml @@ -0,0 +1,7 @@ +server: + servlet: + context-path: / # 应用的访问路径 + port: 8080 + +file: + server-url: http://ser369.g3soft.cn/api diff --git a/code/container-ocr/ocr-springboot/src/main/resources/application-dev.yml b/code/container-ocr/ocr-springboot/src/main/resources/application-dev.yml new file mode 100644 index 00000000..3a86beb4 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/resources/application-dev.yml @@ -0,0 +1,7 @@ +server: + servlet: + context-path: /api/ # 应用的访问路径 + port: 80 + +file: + server-url: http://192.168.5.6:80/api diff --git a/code/container-ocr/ocr-springboot/src/main/resources/application.yml b/code/container-ocr/ocr-springboot/src/main/resources/application.yml index 3b751ab5..9ebdc113 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/application.yml +++ b/code/container-ocr/ocr-springboot/src/main/resources/application.yml @@ -1,13 +1,14 @@ server: servlet: - context-path: / # 应用的访问路径 +# context-path: / # 应用的访问路径 +# context-path: /api/ # 应用的访问路径 tomcat: uri-encoding: UTF-8 # URI编码 accept-count: 1000 # 连接数满后的排队数 threads: max: 800 # 最大线程数 min-spare: 100 # 初始化的线程数 - port: 8080 +# port: 80 spring: servlet: @@ -17,13 +18,15 @@ spring: jackson: time-zone: "Asia/Shanghai" date-format: yyyy-MM-dd HH:mm:ss + profiles: + active: dev file: base-dir: D:/data/file static-path: /static/file -# server-url: http://192.168.1.137:80/api - server-url: http://ser369.g3soft.cn/api +# server-url: http://192.168.5.6:80/api +# server-url: http://ser369.g3soft.cn/api common-dir: common container-dir: container temp-dir: temp \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties index 05bead7b..b3f87b91 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties +++ b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties @@ -1,10 +1,10 @@ -#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR -#username=sa -#password=zhang520.. -#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver - -url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR +url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR;encrypt=false username=sa -password=123456@aaa +password=zhang520.. driver=com.microsoft.sqlserver.jdbc.SQLServerDriver +#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR +#username=sa +#password=123456@aaa +#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver + diff --git a/code/container-ocr/ocr-vue/.env b/code/container-ocr/ocr-vue/.env index f3df623a..4da995fd 100644 --- a/code/container-ocr/ocr-vue/.env +++ b/code/container-ocr/ocr-vue/.env @@ -10,7 +10,7 @@ PUBLIC_SERVER_PORT=8080 # API # 后端 API 基础地址 -PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api -# PUBLIC_API_BASE_URL=http://192.168.1.137:80/api +# PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api +PUBLIC_API_BASE_URL=http://192.168.5.6:80/api PUBLIC_ALIYUN_BUCKET = g3hd diff --git a/code/container-ocr/ocr-vue/components.d.ts b/code/container-ocr/ocr-vue/components.d.ts index 52fa9721..39ff9c4a 100644 --- a/code/container-ocr/ocr-vue/components.d.ts +++ b/code/container-ocr/ocr-vue/components.d.ts @@ -18,7 +18,6 @@ declare module 'vue' { ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] - ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] @@ -28,23 +27,16 @@ declare module 'vue' { ElInput: typeof import('element-plus/es')['ElInput'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] - ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElResult: typeof import('element-plus/es')['ElResult'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] - ElSelect: typeof import('element-plus/es')['ElSelect'] ElSplitter: typeof import('element-plus/es')['ElSplitter'] ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] - ElTable: typeof import('element-plus/es')['ElTable'] - ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTabPane: typeof import('element-plus/es')['ElTabPane'] - ElTabs: typeof import('element-plus/es')['ElTabs'] - ElText: typeof import('element-plus/es')['ElText'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTree: typeof import('element-plus/es')['ElTree'] G3Column: typeof import('./src/components/g3-table/g3-column.vue')['default'] diff --git a/code/container-ocr/ocr-vue/src/views/auth/login.vue b/code/container-ocr/ocr-vue/src/views/auth/login.vue index e9cc9318..1d459565 100644 --- a/code/container-ocr/ocr-vue/src/views/auth/login.vue +++ b/code/container-ocr/ocr-vue/src/views/auth/login.vue @@ -6,11 +6,8 @@ import { useRouter } from "vue-router"; import Vcode from "vue3-puzzle-vcode"; import { reset401Flag } from "@/request"; -import { usePermissionStore } from "@/store/permission"; - const router = useRouter(); const userStore = useUserStore(); -const permissionStore = usePermissionStore(); const form = ref({ orgid: 'g3hd', diff --git a/code/container-ocr/ocr-vue/src/views/container/index.vue b/code/container-ocr/ocr-vue/src/views/container/index.vue index 72252d52..e06bc10f 100644 --- a/code/container-ocr/ocr-vue/src/views/container/index.vue +++ b/code/container-ocr/ocr-vue/src/views/container/index.vue @@ -614,6 +614,7 @@ function buildFileName(containerNo, index, fileName) {