From 4aaa93df7dd2743290e4ef2620158b631f5cb6af Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Thu, 19 Mar 2026 17:28:47 +0800 Subject: [PATCH] u --- code/container-ocr/ocr-springboot/.gitignore | 39 + .../ocr-springboot/.idea/.gitignore | 8 + .../ocr-springboot/.idea/encodings.xml | 7 + .../ocr-springboot/.idea/misc.xml | 14 + .../ocr-springboot/.idea/vcs.xml | 6 + code/container-ocr/ocr-springboot/pom.xml | 92 ++ .../main/java/cn/g3soft/ApiApplication.java | 24 + .../java/cn/g3soft/config/JdbcConfig.java | 19 + .../java/cn/g3soft/config/WebCorsConfig.java | 33 + .../java/cn/g3soft/config/WebMvcConfig.java | 27 + .../config/datasource/DataSourceManager.java | 106 ++ .../g3soft/config/datasource/DruidConfig.java | 70 + .../context/DataSourceContextHolder.java | 17 + .../java/cn/g3soft/context/UserContext.java | 30 + .../cn/g3soft/controller/AuthController.java | 93 ++ .../g3soft/controller/ColumnController.java | 143 ++ .../g3soft/controller/DataListController.java | 132 ++ .../g3soft/controller/DataSaveController.java | 213 +++ .../cn/g3soft/controller/FileController.java | 128 ++ .../cn/g3soft/controller/OcrController.java | 74 + .../g3soft/interceptor/AuthInterceptor.java | 79 + .../interceptor/DataSourceInterceptor.java | 60 + .../main/java/cn/g3soft/utils/DbUtils.java | 641 ++++++++ .../main/java/cn/g3soft/utils/HttpUtils.java | 85 ++ .../main/java/cn/g3soft/utils/JsonUtils.java | 52 + .../src/main/java/cn/g3soft/utils/Result.java | 66 + .../java/cn/g3soft/utils/SpringUtils.java | 157 ++ .../main/java/cn/g3soft/utils/TimeUtils.java | 18 + .../java/cn/g3soft/utils/ValidationUtils.java | 18 + .../java/cn/g3soft/utils/jwt/JwtPayload.java | 13 + .../java/cn/g3soft/utils/jwt/JwtUtils.java | 49 + .../utils/uniqueId/contract/IIdGenerator.java | 5 + .../utils/uniqueId/contract/ISnowWorker.java | 5 + .../contract/IdGeneratorException.java | 25 + .../uniqueId/contract/IdGeneratorOptions.java | 60 + .../uniqueId/contract/OverCostActionArg.java | 45 + .../utils/uniqueId/core/SnowWorkerM1.java | 230 +++ .../utils/uniqueId/core/SnowWorkerM2.java | 37 + .../uniqueId/idgen/DefaultIdGenerator.java | 90 ++ .../g3soft/utils/uniqueId/idgen/UniqueId.java | 33 + .../src/main/resources/application.yml | 16 + .../main/resources/dbconfigs/g3hd.properties | 4 + .../src/main/resources/logback.xml | 75 + .../8B457A96D0F016EAB6EB6D24AD70406C.jpg | Bin 0 -> 8739 bytes code/container-ocr/ocr-uniapp/env/.env | 2 +- ...96D0F016EAB6EB6D24AD70406C_ocr_res_img.jpg | Bin 0 -> 7922 bytes .../8B457A96D0F016EAB6EB6D24AD70406C_res.json | 121 ++ code/container-ocr/ocr-uniapp/package.json | 2 +- code/container-ocr/ocr-uniapp/pnpm-lock.yaml | 319 +++- code/container-ocr/ocr-uniapp/src/App.vue | 4 +- .../ocr-uniapp/src/pages-sub/login/index.vue | 84 +- .../ocr-uniapp/src/pages-sub/upload/index.vue | 331 ++++- code/container-ocr/ocr-uniapp/src/pages.json | 2 +- .../ocr-uniapp/src/pages/account/index.vue | 6 +- .../ocr-uniapp/src/pages/home/index.vue | 50 +- .../ocr-uniapp/src/request/api.ts | 174 +-- .../ocr-uniapp/src/request/index.ts | 21 +- .../ocr-uniapp/src/router/interceptor.ts | 24 +- .../ocr-uniapp/src/static/db.json | 2 +- .../ocr-uniapp/src/style/iconfont.css | 30 +- .../ocr-uniapp/src/style/index.scss | 13 +- .../container-ocr/ocr-uniapp/src/typings.d.ts | 1 + .../ocr-uniapp/src/utils/file.ts | 27 +- code/container-ocr/ocr-uniapp/test.html | 1316 ++++++----------- code/container-ocr/ocr-uniapp/test.py | 18 + code/container-ocr/ocr-uniapp/vite.config.ts | 1 - 66 files changed, 4375 insertions(+), 1311 deletions(-) create mode 100644 code/container-ocr/ocr-springboot/.gitignore create mode 100644 code/container-ocr/ocr-springboot/.idea/.gitignore create mode 100644 code/container-ocr/ocr-springboot/.idea/encodings.xml create mode 100644 code/container-ocr/ocr-springboot/.idea/misc.xml create mode 100644 code/container-ocr/ocr-springboot/.idea/vcs.xml create mode 100644 code/container-ocr/ocr-springboot/pom.xml create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/ApiApplication.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/JdbcConfig.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebCorsConfig.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DataSourceManager.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/config/datasource/DruidConfig.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/DataSourceContextHolder.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/context/UserContext.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/AuthController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/ColumnController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataListController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataSaveController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/AuthInterceptor.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/DataSourceInterceptor.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/DbUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/Result.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/SpringUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ValidationUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtPayload.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtUtils.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IIdGenerator.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/ISnowWorker.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorException.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorOptions.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/OverCostActionArg.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM1.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM2.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/DefaultIdGenerator.java create mode 100644 code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/UniqueId.java create mode 100644 code/container-ocr/ocr-springboot/src/main/resources/application.yml create mode 100644 code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties create mode 100644 code/container-ocr/ocr-springboot/src/main/resources/logback.xml create mode 100644 code/container-ocr/ocr-uniapp/8B457A96D0F016EAB6EB6D24AD70406C.jpg create mode 100644 code/container-ocr/ocr-uniapp/output/8B457A96D0F016EAB6EB6D24AD70406C_ocr_res_img.jpg create mode 100644 code/container-ocr/ocr-uniapp/output/8B457A96D0F016EAB6EB6D24AD70406C_res.json create mode 100644 code/container-ocr/ocr-uniapp/test.py 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> login(@RequestBody Map params){ + String orgid = (String) params.get("orgid"); + String userid = (String) params.get("userid"); + String password = (String) params.get("password"); + + if (StringUtils.isBlank(orgid)) { + return Result.fail("机构码不能为空"); + } + + if (StringUtils.isBlank(userid)) { + return Result.fail("账号不能为空"); + } + + try { + DataSourceManager.getOrCreateDataSource(orgid); + } catch (Exception e) { + return Result.fail("机构码错误"); + } + + DataSourceContextHolder.setDataSourceKey(orgid); + + String sql = "SELECT b_id,b_name,b_password,b_canuse FROM b_user WHERE b_id = ?"; + + List> list = dbUtils.loadData(sql, userid); + + if (list.isEmpty()) { + return Result.fail("账号不存在"); + } + + Map user = list.get(0); + + String dbPassword = Objects.toString(user.get("b_password"), ""); + String inputPassword = Objects.toString(password, ""); + String canuse = Objects.toString(user.get("b_canuse"), ""); + + // 密码校验逻辑 + if (!dbPassword.equals(inputPassword)) { + return Result.fail("密码错误"); + } + + if (!canuse.equals("1")){ + return Result.fail("账号已被禁用"); + } + + // 校验成功 + String token = JwtUtils.createToken(new JwtPayload(orgid, userid)); + + Map map = new HashMap<>(); + + map.put("token",token); + map.put("username",Objects.toString(user.get("b_name"), "")); + + return Result.ok(map); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/ColumnController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/ColumnController.java new file mode 100644 index 00000000..95cb1ae7 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/ColumnController.java @@ -0,0 +1,143 @@ +package cn.g3soft.controller; + +import cn.g3soft.utils.DbUtils; +import cn.g3soft.utils.Result; +import cn.g3soft.utils.ValidationUtils; +import org.springframework.transaction.annotation.Transactional; +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.*; + +@RestController +@RequestMapping("/column") +public class ColumnController { + @Resource + private DbUtils dbUtils; + + /** + * 查询视图列配置接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * @return 数据列表(List 类型),返回视图的列显示配置 + */ + @PostMapping("/view") + public Result>> loadViewColumns(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + String viewName = (String) params.get("viewName"); + + List columns = Arrays.asList( + "col_field_name", + "col_caption", + "col_display_format", + "col_show_summary", + "col_group_index" + ); + + String where = "col_table_name = '" + viewName + "' AND col_visible = '1'"; + + List> data = + dbUtils.loadData( + "s_columnLib", + where, + "col_position desc", + columns + ); + + return Result.ok(data); + } + + /** + * 查询编辑列配置接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * @return 数据列表(List 类型),返回表单编辑列配置 + */ + @PostMapping("/edit") + public Result>> loadEditColumns(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + String viewName = (String) params.get("viewName"); + + List columns = Arrays.asList( + "col_field_name", + "col_field_name_v", + "col_caption", + "col_type", + "col_width", + "col_allow_readonly", + "col_display_format", + + "col_edit_width", + "col_edit_source_data", + "col_edit_source_field", + "col_edit_height", + "col_edit_group_index" + ); + + String where = "col_table_name = '" + viewName + "' AND col_edit = '1'"; + + List> data = + dbUtils.loadData( + "s_columnLib", + where, + "col_edit_position desc", + columns + ); + + return Result.ok(data); + } + + /** + * 查询全部列配置接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * @return 数据列表(List 类型),返回视图的全部列配置 + */ + @PostMapping("/all") + public Result>> loadAllColumns(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + String viewName = (String) params.get("viewName"); + + String where = "col_table_name = '" + viewName + "'"; + List> data = + dbUtils.loadData( + "s_columnLib", + where, + "col_edit_position desc", + Collections.emptyList() + ); + + return Result.ok(data); + } + + /** + * 校验列配置接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * @return 无返回数据,校验完成返回成功结果 + */ + @PostMapping("/check") + @Transactional(rollbackFor = Exception.class) + public Result checkColumns(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + String viewName = (String) params.get("viewName"); + + Map procParams = new HashMap<>(); + procParams.put("viewName", viewName); + + dbUtils.executeProc("G3_CheckColumnSettings_New", procParams, false); + + return Result.ok(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataListController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataListController.java new file mode 100644 index 00000000..0b9cf0e6 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataListController.java @@ -0,0 +1,132 @@ +package cn.g3soft.controller; + +import cn.g3soft.utils.DbUtils; +import cn.g3soft.utils.Result; +import cn.g3soft.utils.ValidationUtils; +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.ArrayList; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/data") +public class DataListController { + @Resource + private DbUtils dbUtils; + + /** + * 加载数据接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * - searchCondition: 查询条件 + * - searchColumns: 查询列(如果未指定,默认查询所有列) + * - orderField: 排序字段 + * @return 数据列表(List 类型),返回查询结果的数据 + */ + @PostMapping("/loadData") + public Result>> loadData(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + String viewName = (String) params.get("viewName"); + String searchCondition = (String) params.get("searchCondition"); + List searchColumns = (List) params.get("searchColumns"); + String orderField = (String) params.get("orderField"); + + List> resultList = dbUtils.loadData(viewName, searchCondition, orderField, searchColumns); + + return Result.ok(resultList); + } + + /** + * 加载数据接口 + * + * @param params 参数集合,包含: + * - sql: SQL 查询语句(必填) + * @return 数据列表(List 类型),返回查询结果的数据 + */ + @PostMapping("loadDataBySql") + public Result>> loadDataBySql(@RequestBody Map params) { + // 校验必填字段 + ValidationUtils.checkRequiredFields(params, "sql"); + + String sql = (String) params.get("sql"); + + // 执行查询并获取数据 + List> list = dbUtils.loadData(sql); + + return Result.ok(list); + } + + /** + * 分页查询接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * - page: 页码(默认 1) + * - size: 每页条数(默认 10) + * - searchCondition: 查询条件 + * - searchColumns: 查询列(未指定时默认查询所有列) + * - orderField: 排序字段 + * @return 分页结果(Map 类型),包含: + * - total: 总记录数 + * - rows: 数据列表 + */ + @PostMapping("page") + public Result> page(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + + Integer page = (Integer) params.getOrDefault("page", 1); + Integer size = (Integer) params.getOrDefault("size", 10); + + String viewName = (String) params.get("viewName"); + String searchCondition = (String) params.get("searchCondition"); + List searchColumns = (List) params.get("searchColumns"); + String orderField = (String) params.get("orderField"); + + Map result = dbUtils.page(page, size, viewName, searchCondition, orderField, searchColumns); + + return Result.ok(result); + } + + /** + * 树形数据查询接口 + * + * @param params 参数集合,包含: + * - viewName: 视图名称(必填) + * - id: 当前节点 ID(为空时查询根节点) + * - js: 当前层级 + * @return 数据列表(List 类型),返回当前节点的子节点数据 + */ + @PostMapping("tree") + public Result>> loadTreeData(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "viewName"); + String viewName = (String) params.get("viewName"); + String id = (String) params.get("id"); + Integer js = (Integer) params.get("js"); + + StringBuilder sql = new StringBuilder("SELECT * FROM ").append(viewName).append(" WHERE js = ?"); + List sqlParams = new ArrayList<>(); + + if (StringUtils.isNotBlank(id)) { + js++; + } + + sqlParams.add(js); + + if (StringUtils.isNotBlank(id)) { + sql.append(" AND b_id LIKE ?"); + sqlParams.add(id + "_%"); + } + + List> data = dbUtils.loadData(sql.toString(), sqlParams.toArray()); + + return Result.ok(data); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataSaveController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataSaveController.java new file mode 100644 index 00000000..9b0fb904 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/DataSaveController.java @@ -0,0 +1,213 @@ +package cn.g3soft.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cn.g3soft.context.UserContext; +import cn.g3soft.utils.DbUtils; +import cn.g3soft.utils.Result; +import cn.g3soft.utils.TimeUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.transaction.annotation.Transactional; +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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/data") +public class DataSaveController { + @Resource + private DbUtils dbUtils; + + // 单主键 + @PostMapping("/saveObject") + @Transactional(rollbackFor = Exception.class) + public Result saveObject(@RequestBody List> params) throws JsonProcessingException { + if (ObjectUtils.isEmpty(params)) { + return Result.ok(); + } + + String userId = UserContext.get().getUserId(); + String currentDateTime = TimeUtils.getDateTime(); + + List> logList = new ArrayList<>(); + + for (Map param : params) { + String tableName = (String) param.get("tableName"); + String keyField = (String) param.get("keyField"); + List> insertList = (List>) param.get("inserts"); + List> updateList = (List>) param.get("updates"); + List> deleteList = (List>) param.get("deletes"); + + // enable 数字控制:0 不生成日志,其余或未传默认开启 + boolean logEnabled = !"0".equals(param.get("log")); // 日志开关,默认开启 + boolean userEnable = !"0".equals(param.get("user")); // 操作人开关,默认开启 + + // 插入 + if (ObjectUtils.isNotEmpty(insertList)) { + for (Map insertMap : insertList) { + if (userEnable){ + if (keyField.equals("subid")) { + insertMap.put("mx_inputuser_id", userId); + insertMap.put("mx_inputdatetime", currentDateTime); + } else { + insertMap.put("b_inputuser_id", userId); + insertMap.put("b_inputdatetime", currentDateTime); + } + } + + dbUtils.insert(tableName, keyField, insertMap); + } + + if (logEnabled){ + addOperationLog(logList,tableName,"INSERT",insertList); + } + } + + // 更新 + if (ObjectUtils.isNotEmpty(updateList)) { + for (Map updateMap : updateList) { + if (userEnable) { + if (keyField.equals("subid")) { + updateMap.put("mx_updateuser_id", userId); + updateMap.put("mx_updatedatetime", currentDateTime); + } else { + updateMap.put("b_updateuser_id", userId); + updateMap.put("b_updatedatetime", currentDateTime); + } + } + + dbUtils.update(tableName, keyField, updateMap); + } + + if (logEnabled) { + addOperationLog(logList, tableName, "UPDATE", updateList); + } + } + + // 删除 + if (ObjectUtils.isNotEmpty(deleteList)) { + for (Map deleteMap : deleteList) { + dbUtils.deleteById(tableName, keyField, deleteMap.get(keyField)); + } + + if (logEnabled) { + addOperationLog(logList, tableName, "DELETE", deleteList); + } + } + + } + + if (!logList.isEmpty()) { + dbUtils.batchInsert("log_datas", logList); + } + + return Result.ok(); + } + + // 多主键 + @PostMapping("/saveObjectKeys") + @Transactional(rollbackFor = Exception.class) + public Result saveObjectKeys(@RequestBody List> params) throws JsonProcessingException { + if (ObjectUtils.isEmpty(params)) { + return Result.ok(); + } + + String userId = UserContext.get().getUserId(); + String currentDateTime = TimeUtils.getDateTime(); + + List> logList = new ArrayList<>(); + + for (Map param : params) { + String tableName = (String) param.get("tableName"); + List keyFields = (List) param.get("keyFields"); + List> insertList = (List>) param.get("inserts"); + List> updateList = (List>) param.get("updates"); + List> deleteList = (List>) param.get("deletes"); + // enable 数字控制:0 不生成日志,其余或未传默认开启 + boolean logEnabled = !"0".equals(param.get("log")); // 日志开关,默认开启 + boolean userEnable = !"0".equals(param.get("user")); // 操作人开关,默认开启 + + // 插入 + if (ObjectUtils.isNotEmpty(insertList)) { + for (Map insertMap : insertList) { + if(userEnable){ + if (keyFields.contains("subid")) { + insertMap.put("mx_inputuser_id", userId); + insertMap.put("mx_inputdatetime", currentDateTime); + } else { + insertMap.put("b_inputuser_id", userId); + insertMap.put("b_inputdatetime", currentDateTime); + } + } + + dbUtils.insert(tableName, keyFields, insertMap); + } + + if (logEnabled) { + addOperationLog(logList, tableName, "INSERT", insertList); + } + } + + // 更新 + if (ObjectUtils.isNotEmpty(updateList)) { + for (Map updateMap : updateList) { + if(userEnable) { + if (keyFields.contains("subid")) { + updateMap.put("mx_updateuser_id", userId); + updateMap.put("mx_updatedatetime", currentDateTime); + } else { + updateMap.put("b_updateuser_id", userId); + updateMap.put("b_updatedatetime", currentDateTime); + } + } + + dbUtils.update(tableName, keyFields, updateMap); + } + if (logEnabled) { + addOperationLog(logList, tableName, "UPDATE", updateList); + } + } + + // 删除 + if (ObjectUtils.isNotEmpty(deleteList)) { + for (Map deleteMap : deleteList) { + dbUtils.delete(tableName,deleteMap); + } + if (logEnabled) { + addOperationLog(logList, tableName, "DELETE", deleteList); + } + } + + } + + if (!logList.isEmpty()){ + dbUtils.batchInsert("log_datas",logList); + } + + return Result.ok(); + } + + + private void addOperationLog(List> logList,String table, String operation, List> data) throws JsonProcessingException { + if (ObjectUtils.isEmpty(data)){ + return; + } + + ObjectMapper objectMapper = new ObjectMapper(); + String dataStr = objectMapper.writeValueAsString(data); + + Map mapData = new HashMap<>(); + mapData.put("b_table",table); + mapData.put("b_option",operation); + mapData.put("b_datas",dataStr); + + logList.add(mapData); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java new file mode 100644 index 00000000..2076a0de --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/FileController.java @@ -0,0 +1,128 @@ +package cn.g3soft.controller; + +import cn.g3soft.context.UserContext; +import cn.g3soft.utils.*; +import cn.g3soft.utils.uniqueId.idgen.UniqueId; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/file") +public class FileController { + @Resource + private DbUtils dbUtils; + + @PostMapping("/getAliyunKey") + public Result> getAliyunKey(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "bucket"); + + String bucket = (String) params.get("bucket"); + + String orgId = UserContext.get().getOrgId(); + + String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucket; + String reqResult = HttpUtils.sendGet(requestUrl); + + Map objectMap = JsonUtils.toMap(reqResult); + + return Result.ok(objectMap); + } + + @PostMapping("/insert") + @Transactional(rollbackFor = Exception.class) + public Result insertFile(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid"); + + String userId = UserContext.get().getUserId(); + + params.put("subid", UniqueId.nextId()); + params.put("mx_inputuser_id",userId); + params.put("mx_inputdatetime", TimeUtils.getDateTime()); + + dbUtils.insert("bf_file","subid",params); + + return Result.ok(); + } + @PostMapping("/delete") + @Transactional(rollbackFor = Exception.class) + public Result deleteFile(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "id", "tableName", "bucketName"); + + String tableName = params.get("tableName").toString(); + String bucketName = params.get("bucketName").toString(); + String id = params.get("id").toString(); + + // 查询数据 + List> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id); + + // 如果没有数据,直接返回删除成功 + if (ObjectUtils.isEmpty(maps)) { + return Result.ok("删除成功"); + } + + String mxMapfilename = maps.get(0).get("mx_mapfilename").toString(); + String orgId = UserContext.get().getOrgId(); + int index = mxMapfilename.indexOf(orgId + "/"); + + // 如果没找到,就转换orgId为大写 + if (index < 0) { + orgId = orgId.toUpperCase(); + index = mxMapfilename.indexOf(orgId + "/"); + } + + String objectName = mxMapfilename.substring(index); + + // 删除记录 + Map deleteParams = new HashMap<>(); + deleteParams.put("subid", id); + int deleteCount = dbUtils.delete(tableName, deleteParams); + + // 如果删除成功,发送删除请求 + if (deleteCount > 0) { + String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName); + try { + HttpUtils.sendGet(requestUrl); + } catch (Exception e) { + throw new RuntimeException("删除文件失败",e); + } + } + + return Result.ok(); + } + + @PostMapping("/deleteAliyunFile") + @Transactional(rollbackFor = Exception.class) + public Result deleteAliyunFile(@RequestBody Map params) { + ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename"); + + String bucketName = params.get("bucketName").toString(); + String mxMapfilename = params.get("mx_mapfilename").toString(); + String orgId = UserContext.get().getOrgId(); + + int index = mxMapfilename.indexOf(orgId + "/"); + // 如果没找到,就转换orgId为大写 + if (index < 0) { + orgId = orgId.toUpperCase(); + index = mxMapfilename.indexOf(orgId + "/"); + } + + String objectName = mxMapfilename.substring(index); + + // 发送删除请求 + String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName); + + try { + HttpUtils.sendGet(requestUrl); + } catch (Exception e) { + throw new RuntimeException("删除文件失败",e); + } + + return Result.ok(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java new file mode 100644 index 00000000..555da14f --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/controller/OcrController.java @@ -0,0 +1,74 @@ +package cn.g3soft.controller; + +import cn.g3soft.utils.HttpUtils; +import cn.g3soft.utils.JsonUtils; +import cn.g3soft.utils.Result; +import cn.g3soft.utils.ValidationUtils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/ocr") +public class OcrController { + @PostMapping("/box") + public Result> boxOcr(@RequestBody Map params){ + ValidationUtils.checkRequiredFields(params, "fileUrl"); + + String fileUrl = (String) params.get("fileUrl"); + + String requestUrl = "http://116.204.97.131:80/ocr"; + + Map reqPayload = new HashMap<>(); + reqPayload.put("file", fileUrl); + reqPayload.put("fileType", 1); + reqPayload.put("visualize", false); + + try { + String response = HttpUtils.sendPostJson(requestUrl, reqPayload); + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(response); + + JsonNode ocrResults = root.path("result").path("ocrResults"); + if (!ocrResults.isArray() || ocrResults.isEmpty()) { + return Result.ok(Collections.emptyMap()); + } + + String number = ""; + double maxScore = -1.0; + + for (JsonNode item : ocrResults) { + JsonNode pruned = item.path("prunedResult"); + JsonNode recTexts = pruned.path("rec_texts"); + JsonNode recScores = pruned.path("rec_scores"); + + for (int i = 0; i < recTexts.size(); i++) { + double score = recScores.get(i).asDouble(); + if (score > maxScore) { + maxScore = score; + number = recTexts.get(i).asText(); + } + } + } + if (maxScore < 0) { + return Result.ok(Collections.emptyMap()); + } + + Map result = new HashMap<>(); + result.put("boxNumber", number); + result.put("score", maxScore); + + return Result.ok(result); + } catch (Exception e){ + return Result.fail("OCR 识别失败,请联系管理员"); + } + + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/AuthInterceptor.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/AuthInterceptor.java new file mode 100644 index 00000000..99ae4f06 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/AuthInterceptor.java @@ -0,0 +1,79 @@ +package cn.g3soft.interceptor; + +import cn.g3soft.context.UserContext; +import cn.g3soft.utils.JsonUtils; +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.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +public class AuthInterceptor implements HandlerInterceptor { + /*** + * 在请求处理之前进行调用(Controller方法调用之前) + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //OPTIONS方法是HTTP协议中定义的一种请求方法。它用于在客户端和服务器之间发起一个预检请求(Preflight Request),以确定实际的请求是否被服务器接受。 + String method = request.getMethod(); + if("OPTIONS".equalsIgnoreCase(method)){ + return true; + } + + boolean flag = false; + + try { + String token = request.getHeader("Authorization"); + if (token.startsWith("Bearer ")) { + // 去掉 "Bearer " 前缀,获取 token 部分 + token = token.substring(7); + // 继续处理 token,例如验证、解码等操作 + } + + if(!StringUtils.isBlank(token)){ + // 验证token + JwtPayload jwtPayload = JwtUtils.parseToken(token); + + if (jwtPayload != null){ + flag = true; + UserContext.set(jwtPayload.getUserId(), jwtPayload.getOrgId()); + } + } + }catch (Exception e){ + // 登录失败 + } + + if(!flag){ + Result result = Result.fail(Result.AUTH_FAIL_CODE,"登录已过期"); + + String jsonString = JsonUtils.toJson(result); + response.setContentType("application/json;charset=utf-8"); + PrintWriter out = response.getWriter(); + out.println(jsonString); + out.flush(); + out.close(); + } + return flag; + } + + /*** + * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) + */ + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { +// System.out.println("执行了拦截器的postHandle方法"); + } + + /*** + * 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作) + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + UserContext.remove(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/DataSourceInterceptor.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/DataSourceInterceptor.java new file mode 100644 index 00000000..b69cb918 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/interceptor/DataSourceInterceptor.java @@ -0,0 +1,60 @@ +package cn.g3soft.interceptor; + +import cn.g3soft.config.datasource.DataSourceManager; +import cn.g3soft.context.DataSourceContextHolder; +import cn.g3soft.context.UserContext; +import cn.g3soft.utils.JsonUtils; +import cn.g3soft.utils.Result; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +public class DataSourceInterceptor implements HandlerInterceptor { + /*** + * 在请求处理之前进行调用(Controller方法调用之前) + */ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + boolean flag = false; + + try { + UserContext userContext = UserContext.get(); + String orgId = userContext.getOrgId(); + + DataSourceManager.getOrCreateDataSource(orgId); + DataSourceContextHolder.setDataSourceKey(orgId); + + flag = true; + } catch (Exception e){ + Result result = Result.fail(Result.AUTH_FAIL_CODE,"机构码错误,请重新登录"); + + String jsonString = JsonUtils.toJson(result); + response.setContentType("application/json;charset=utf-8"); + PrintWriter out = response.getWriter(); + out.println(jsonString); + out.flush(); + out.close(); + } + + return flag; + } + + /*** + * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) + */ + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { +// System.out.println("执行了拦截器的postHandle方法"); + } + + /*** + * 整个请求结束之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(主要用于进行资源清理工作) + */ + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + DataSourceContextHolder.clearDataSourceKey(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/DbUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/DbUtils.java new file mode 100644 index 00000000..5084689d --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/DbUtils.java @@ -0,0 +1,641 @@ +package cn.g3soft.utils; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcCall; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Component +public class DbUtils { + @Resource + private JdbcTemplate jdbcTemplate; + + private static final Pattern ILLEGAL_SQL_PATTERN = Pattern.compile( + "(?i)\\b(delete|update|insert|drop|truncate|alter|create|exec|execute|grant|revoke)\\b" + ); + + public static final Pattern SAFE_IDENTIFIER = + Pattern.compile("^[a-zA-Z0-9_]+$"); + + public void validateIdentifier(String name) { + if (StringUtils.isBlank(name)) { + return; + } + + if (!SAFE_IDENTIFIER.matcher(name).matches()) { + throw new RuntimeException("非法标识符: " + name); + } + } + + /** + * 查询数据 + */ + public List> loadData( + String viewName, + String searchCondition, + String orderField, + List searchColumns + ) { + validateIdentifier(viewName); + + String columnsSql = (searchColumns == null || searchColumns.isEmpty()) + ? "*" + : searchColumns.stream() + .peek(this::validateIdentifier) + .map(c -> "[" + c + "]") + .collect(Collectors.joining(", ")); + + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("SELECT ") + .append(columnsSql) + .append(" FROM ") + .append(viewName); + + if (StringUtils.isNotBlank(searchCondition)) { + sqlBuilder.append(" WHERE ").append(searchCondition); + } + + if (StringUtils.isNotBlank(orderField)) { + sqlBuilder.append(" ORDER BY ").append(orderField); + } + + String sql = sqlBuilder.toString(); + + validateFinalSql(sql); + + return jdbcTemplate.queryForList(sql); + } + + public List> loadData(String sql) { + validateFinalSql(sql); + + return jdbcTemplate.queryForList(sql); + } + + public List> loadData(String sql, Object... params) { + // 校验 SQL 合法性 + validateFinalSql(sql); + + if (params == null || params.length == 0) { + // 如果没有参数,直接调用原来的 queryForList + return jdbcTemplate.queryForList(sql); + } + + // 使用 JdbcTemplate 的 queryForList 支持预编译参数 + return jdbcTemplate.queryForList(sql, params); + } + + /** + * 分页通用方法 + * @param page 页数 + * @param size 条数 + * @param viewName 视图名称 + * @param searchCondition 搜索条件 + * @param orderField 排序条件 + * @param searchColumns 搜索列(字符串数组,没传默认查 *) + * @return Map {total: 总条数, rows: 数据列表} + */ + public Map page( + int page, + int size, + String viewName, + String searchCondition, + String orderField, + List searchColumns + ) { + validateIdentifier(viewName); + validateIdentifier(orderField); + + if (page < 1) page = 1; + if (size <= 0) size = 10; + + int start = (page - 1) * size + 1; + int end = page * size; + + List params = new ArrayList<>(); + + // ====================== + // SELECT 字段 + // ====================== + String selectSql; + String outerSelectSql; + + if (searchColumns != null && !searchColumns.isEmpty()) { + + searchColumns.forEach(this::validateIdentifier); + + selectSql = searchColumns.stream() + .map(c -> "t." + c) + .collect(Collectors.joining(",")); + + outerSelectSql = searchColumns.stream() + .map(c -> "sub." + c) + .collect(Collectors.joining(",")); + + } else { + + selectSql = "t.*"; + outerSelectSql = "sub.*"; + } + + // ====================== + // WHERE 条件 + // ====================== + StringBuilder whereSql = new StringBuilder("1=1"); + + if (StringUtils.isNotBlank(searchCondition)) { + whereSql.append(" AND ").append(searchCondition); + } + + // ====================== + // ORDER BY + // ====================== + String orderSql; + + if (StringUtils.isBlank(orderField)) { + orderSql = "(SELECT NULL)"; + } else { + orderSql = "t." + orderField; + } + + // ====================== + // SQLServer 2008 分页 + // ====================== + String sql = + "SELECT " + outerSelectSql + " FROM (" + + " SELECT ROW_NUMBER() OVER (ORDER BY " + orderSql + ") AS row_num, " + + selectSql + + " FROM " + viewName + " t " + + " WHERE " + whereSql + + ") sub WHERE sub.row_num BETWEEN ? AND ?"; + + params.add(start); + params.add(end); + + validateFinalSql(sql); + + List> list = + jdbcTemplate.queryForList(sql, params.toArray()); + + // ====================== + // COUNT + // ====================== + String countSql = + "SELECT COUNT(*) FROM " + viewName + " t WHERE " + whereSql; + + Integer total = jdbcTemplate.queryForObject( + countSql, + Integer.class + ); + + Map result = new HashMap<>(); + + result.put("total", total); + result.put("rows", list); + + return result; + } + + public void validateFinalSql(String sql) { + if (StringUtils.isBlank(sql)) { + return; + } + + String lowerSql = sql.toLowerCase(); + + if (!lowerSql.trim().startsWith("select")) { + throw new RuntimeException("只允许执行SELECT查询"); + } + + Matcher matcher = ILLEGAL_SQL_PATTERN.matcher(sql); + if (matcher.find()) { + throw new RuntimeException("检测到非法SQL关键字: " + matcher.group()); + } + + if (sql.contains(";")) { + throw new RuntimeException("SQL包含非法字符 ';'"); + } + + if (sql.contains("--") || sql.contains("/*") || sql.contains("*/")) { + throw new RuntimeException("SQL包含非法注释"); + } + } + + /** + * 插入 + */ + public int insert(String tableName, String pkColumn, Map data) { + validateIdentifier(tableName); + validateIdentifier(pkColumn); + + if (data == null || data.isEmpty()) { + throw new RuntimeException("插入数据不能为空"); + } + + Object pkValue = getPkValue(data, pkColumn); + + if(ObjectUtils.isEmpty(pkValue)){ + throw new RuntimeException("主键不能为空"); + } + + String columns = data.keySet() + .stream() + .peek(this::validateIdentifier) + .collect(Collectors.joining(",")); + + String placeholders = data.keySet() + .stream() + .map(k -> "?") + .collect(Collectors.joining(",")); + + String sql = "INSERT INTO " + tableName + + " (" + columns + ") VALUES (" + placeholders + ")"; + + return jdbcTemplate.update(sql, data.values().toArray()); + } + + /** + * 插入(支持多主键) + */ + public int insert(String tableName, Collection pkColumns, Map data) { + + validateIdentifier(tableName); + + if (data == null || data.isEmpty()) { + throw new RuntimeException("插入数据不能为空"); + } + + if (pkColumns == null || pkColumns.isEmpty()) { + throw new RuntimeException("主键不能为空"); + } + + // 校验主键 + for (String pk : pkColumns) { + validateIdentifier(pk); + + Object pkValue = getPkValue(data, pk); + if (ObjectUtils.isEmpty(pkValue)) { + throw new RuntimeException("主键不能为空:" + pk); + } + } + + String columns = data.keySet() + .stream() + .peek(this::validateIdentifier) + .collect(Collectors.joining(",")); + + String placeholders = data.keySet() + .stream() + .map(k -> "?") + .collect(Collectors.joining(",")); + + String sql = "INSERT INTO " + tableName + + " (" + columns + ") VALUES (" + placeholders + ")"; + + return jdbcTemplate.update(sql, data.values().toArray()); + } + + /** + * 更新 + */ + public int update(String tableName, String pkField, Map data) { + validateIdentifier(tableName); + validateIdentifier(pkField); + + Object pkValue = getPkValue(data, pkField); + + List updateFields = getUpdateFields(data, pkField); + + String sql = buildUpdateSql(tableName, pkField, updateFields); + + Object[] params = buildParams(updateFields, data, pkValue); + + return jdbcTemplate.update(sql, params); + } + + /** + * 更新(支持多主键) + */ + public int update(String tableName, Collection pkFields, Map data) { + + validateIdentifier(tableName); + + if (pkFields == null || pkFields.isEmpty()) { + throw new RuntimeException("主键不能为空"); + } + + // 校验主键 + Map pkValues = new LinkedHashMap<>(); + + for (String pk : pkFields) { + validateIdentifier(pk); + + Object value = getPkValue(data, pk); + if (ObjectUtils.isEmpty(value)) { + throw new RuntimeException("主键不能为空:" + pk); + } + + pkValues.put(pk, value); + } + + // 更新字段(排除主键) + List updateFields = data.keySet() + .stream() + .filter(k -> !pkFields.contains(k)) + .peek(this::validateIdentifier) + .collect(Collectors.toList()); + + if (updateFields.isEmpty()) { + throw new RuntimeException("没有可更新字段"); + } + + // SET + String setClause = updateFields.stream() + .map(f -> f + "=?") + .collect(Collectors.joining(",")); + + // WHERE + String whereClause = pkFields.stream() + .map(pk -> pk + "=?") + .collect(Collectors.joining(" AND ")); + + String sql = "UPDATE " + tableName + + " SET " + setClause + + " WHERE " + whereClause; + + // 参数 + List params = new ArrayList<>(); + + for (String field : updateFields) { + params.add(data.get(field)); + } + + params.addAll(pkValues.values()); + + return jdbcTemplate.update(sql, params.toArray()); + } + + /** + * 批量更新 + */ + public int[] batchUpdate(String tableName, String pkField, List> dataList) { + if (dataList == null || dataList.isEmpty()) { + return new int[0]; + } + + validateIdentifier(tableName); + validateIdentifier(pkField); + + List updateFields = getUpdateFields(dataList.get(0), pkField); + + String sql = buildUpdateSql(tableName, pkField, updateFields); + + List batchArgs = new ArrayList<>(); + + for (Map data : dataList) { + + Object pkValue = getPkValue(data, pkField); + + batchArgs.add(buildParams(updateFields, data, pkValue)); + } + + return jdbcTemplate.batchUpdate(sql, batchArgs); + } + + /** + * 删除 + */ + public int deleteById(String tableName, String pkField, Object pkValue) { + validateIdentifier(tableName); + validateIdentifier(pkField); + + if (pkValue == null) { + throw new IllegalArgumentException("主键不能为空: " + pkField); + } + + String sql = "DELETE FROM " + tableName + + " WHERE " + pkField + "=?"; + + return jdbcTemplate.update(sql, pkValue); + } + + /** + * 删除(支持多主键) + */ + public int delete(String tableName, Map pkValues) { + + validateIdentifier(tableName); + + if (pkValues == null || pkValues.isEmpty()) { + throw new IllegalArgumentException("主键不能为空"); + } + + for (Map.Entry entry : pkValues.entrySet()) { + validateIdentifier(entry.getKey()); + + if (entry.getValue() == null) { + throw new IllegalArgumentException("主键不能为空: " + entry.getKey()); + } + } + + String whereClause = pkValues.keySet() + .stream() + .map(k -> k + "=?") + .collect(Collectors.joining(" AND ")); + + String sql = "DELETE FROM " + tableName + + " WHERE " + whereClause; + + return jdbcTemplate.update(sql, pkValues.values().toArray()); + } + + /** + * 批量插入 + * + * @param tableName 表名 + * @param dataList 数据列表 + * @return 每条记录影响行数 + */ + public int[] batchInsert(String tableName, List> dataList) { + if (dataList == null || dataList.isEmpty()) { + return new int[0]; + } + + validateIdentifier(tableName); + + Map first = dataList.get(0); + + List fields = new ArrayList<>(first.keySet()); + + fields.forEach(this::validateIdentifier); + + String columns = String.join(",", fields); + + String placeholders = fields.stream() + .map(f -> "?") + .collect(Collectors.joining(",")); + + String sql = "INSERT INTO " + tableName + + " (" + columns + ") VALUES (" + placeholders + ")"; + + List batchArgs = new ArrayList<>(); + + for (Map data : dataList) { + + Object[] params = new Object[fields.size()]; + + for (int i = 0; i < fields.size(); i++) { + params[i] = data.get(fields.get(i)); + } + + batchArgs.add(params); + } + + return jdbcTemplate.batchUpdate(sql, batchArgs); + } + + /** + * 批量删除 + * + * @param tableName 表名 + * @param pkField 主键字段 + * @param ids 主键集合 + * @return 删除行数 + */ + public int batchDeleteByIds(String tableName, String pkField, List ids) { + + if (ids == null || ids.isEmpty()) { + return 0; + } + + validateIdentifier(tableName); + validateIdentifier(pkField); + + String placeholders = ids.stream() + .map(i -> "?") + .collect(Collectors.joining(",")); + + String sql = "DELETE FROM " + tableName + + " WHERE " + pkField + " IN (" + placeholders + ")"; + + return jdbcTemplate.update(sql, ids.toArray()); + } + + /** + * 执行存储过程 + * + * @param procName 存储过程名 + * @param params 存储过程参数,按顺序或按名字传入 + * @param returnsResultSet 是否返回结果集 + * @return 返回结果集或空 + */ + public List> executeProc(String procName, Map params, boolean returnsResultSet) { + if (returnsResultSet) { + // 如果返回结果集,使用 SimpleJdbcCall + SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName(procName); + Map result = call.execute(params); + + // 获取第一个返回结果集,如果没有就返回空列表 + if (!result.isEmpty()) { + Object first = result.values().iterator().next(); + if (first instanceof List) { + return (List>) first; + } + } + return Collections.emptyList(); + } else { + // 不返回结果集,用 update 执行 + StringBuilder sqlBuilder = new StringBuilder("{call ").append(procName).append("("); + + if (params != null && !params.isEmpty()) { + for (int i = 0; i < params.size(); i++) { + sqlBuilder.append("?,"); + } + sqlBuilder.deleteCharAt(sqlBuilder.length() - 1); // 删除最后一个逗号 + } + + sqlBuilder.append(")}"); + + jdbcTemplate.update(sqlBuilder.toString(), params != null ? params.values().toArray() : new Object[]{}); + return Collections.emptyList(); + } + } + + /** + * 获取主键值 + */ + private Object getPkValue(Map data, String pkField) { + Object pkValue = data.get(pkField); + + if (pkValue == null) { + throw new RuntimeException("主键不能为空: " + pkField); + } + + return pkValue; + } + + /** + * 获取更新字段 + */ + private List getUpdateFields(Map data, String pkField) { + + List fields = data.entrySet() + .stream() + .filter(e -> !e.getKey().equalsIgnoreCase(pkField)) + .filter(e -> e.getValue() != null) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (fields.isEmpty()) { + throw new RuntimeException("没有可更新的字段"); + } + + return fields; + } + + /** + * 构建更新SQL + */ + private String buildUpdateSql(String tableName, String pkField, List fields) { + + String setClause = fields.stream() + .map(f -> f + "=?") + .collect(Collectors.joining(",")); + + return "UPDATE " + tableName + + " SET " + setClause + + " WHERE " + pkField + "=?"; + } + + /** + * 构建参数 + */ + private Object[] buildParams(List fields, Map data, Object pkValue) { + + Object[] params = new Object[fields.size() + 1]; + + for (int i = 0; i < fields.size(); i++) { + + Object value = data.get(fields.get(i)); + + if (value == null) { + throw new RuntimeException("字段值不能为空: " + fields.get(i)); + } + + params[i] = value; + } + + params[fields.size()] = pkValue; + + return params; + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java new file mode 100644 index 00000000..73242d50 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/HttpUtils.java @@ -0,0 +1,85 @@ +package cn.g3soft.utils; + + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import javax.net.ssl.*; +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Map; + +@Slf4j +public class HttpUtils { + + private static String request(String method, String url, String body, String contentType) { + HttpURLConnection conn = null; + StringBuilder result = new StringBuilder(); + + try { + URL realUrl = new URL(url); + conn = (HttpURLConnection) realUrl.openConnection(); + + conn.setRequestMethod(method); + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0"); + conn.setRequestProperty("Content-Type", contentType); + + conn.setDoInput(true); + + // 👉 有 body 才写 + if (body != null && !body.isEmpty()) { + conn.setDoOutput(true); + try (OutputStreamWriter out = + new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8)) { + out.write(body); + out.flush(); + } + } + + try (BufferedReader in = new BufferedReader( + new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { + + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + } + } catch (Exception e) { + log.error("HTTP 请求失败", e); + throw new RuntimeException(e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + + return result.toString(); + } + + public static String sendGet(String url) { + return request("GET", url, null, "application/x-www-form-urlencoded"); + } + + public static String sendGet(String url, String param) { + String fullUrl = StringUtils.isNotBlank(param) ? url + "?" + param : url; + return request("GET", fullUrl, null, "application/x-www-form-urlencoded"); + } + + public static String sendPost(String url, String param) { + return request("POST", url, param, "application/x-www-form-urlencoded; charset=UTF-8"); + } + + public static String sendPostJson(String url, Map params) { + String json = JsonUtils.toJson(params); + return request("POST", url, json, "application/json; charset=UTF-8"); + } + +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java new file mode 100644 index 00000000..23be4c8d --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/JsonUtils.java @@ -0,0 +1,52 @@ +package cn.g3soft.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Map; + +public class JsonUtils { + private static final ObjectMapper objectMapper = new ObjectMapper(); + // 判断字符串是否为有效的 JSON 格式 + public static boolean isTypeJSON(String msgText) { + try { + // 尝试将字符串解析为 JsonNode,如果能解析,说明是有效的 JSON + JsonNode node = objectMapper.readTree(msgText); + return node != null; + } catch (Exception e) { + return false; // 如果抛出异常,说明不是有效的 JSON + } + } + + // 将 JSON 字符串转换为指定类型的 Java 对象 + public static T toBean(String json, Class clazz) { + try { + // 使用 ObjectMapper 将 JSON 字符串转换为指定类型的对象 + return objectMapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + e.printStackTrace(); // 打印异常信息 + return null; // 如果转换失败,返回 null + } + } + + // 将对象转换为 JSON 字符串 + public static String toJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); // 将对象转换为 JSON 字符串 + } catch (JsonProcessingException e) { + System.out.println(e.getMessage()); + return null; // 如果出现异常,返回 null + } + } + + public static Map toMap(String json) { + try { + return objectMapper.readValue(json, new TypeReference>() {}); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/Result.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/Result.java new file mode 100644 index 00000000..2b24df53 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/Result.java @@ -0,0 +1,66 @@ +package cn.g3soft.utils; + +import lombok.Data; +import lombok.NonNull; + +@Data +public class Result { + + private Integer code; // 状态码 + private String message; // 消息 + @NonNull + private T data; // 数据,可空 + + // 成功和失败默认状态码 + public static final int SUCCESS_CODE = 200; + + public static final int AUTH_FAIL_CODE = 401; + + public static final int SYSTEM_ERROR_CODE = 500; + public static final int BUSINESS_ERROR_CODE = 1000; + + public Result() { + } + + public Result(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + // ---------------- 成功 ---------------- + + public static Result ok() { + return new Result<>(SUCCESS_CODE, "ok", null); + } + + public static Result ok(String message) { + return new Result<>(SUCCESS_CODE, message, null); + } + + public static Result ok(T data) { + return new Result<>(SUCCESS_CODE, "ok", data); + } + + public static Result ok(String message, T data) { + return new Result<>(SUCCESS_CODE, message, data); + } + + // ---------------- 失败 ---------------- + + public static Result fail() { + return new Result<>(BUSINESS_ERROR_CODE, "fail", null); + } + + public static Result fail(String message) { + return new Result<>(BUSINESS_ERROR_CODE, message, null); + } + + public static Result fail(Integer code, String message) { + return new Result<>(code, message, null); + } + + public static Result fail(String message, T data) { + return new Result<>(BUSINESS_ERROR_CODE, message, data); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/SpringUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/SpringUtils.java new file mode 100644 index 00000000..3e859ed4 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/SpringUtils.java @@ -0,0 +1,157 @@ +package cn.g3soft.utils; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author webserver + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return activeProfiles.length > 0 ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java new file mode 100644 index 00000000..35c78e89 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/TimeUtils.java @@ -0,0 +1,18 @@ +package cn.g3soft.utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class TimeUtils { + private static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 获取当前时间(年月日时分秒) + * + * @return 当前时间字符串,格式:yyyy-MM-dd HH:mm:ss + */ + public static String getDateTime() { + return LocalDateTime.now().format(DATE_TIME_FORMATTER); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ValidationUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ValidationUtils.java new file mode 100644 index 00000000..26398113 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/ValidationUtils.java @@ -0,0 +1,18 @@ +package cn.g3soft.utils; + +import java.util.Map; + +public class ValidationUtils { + + public static boolean checkRequiredFields(Map body, String... requiredFields) { + for (String field : requiredFields) { + Object value = body.get(field); + + if (value == null || value.toString().trim().isEmpty()) { + return false; + } + } + return true; + } + +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtPayload.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtPayload.java new file mode 100644 index 00000000..ccdd0b1d --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtPayload.java @@ -0,0 +1,13 @@ +package cn.g3soft.utils.jwt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class JwtPayload { + private String orgId; + private String userId; +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtUtils.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtUtils.java new file mode 100644 index 00000000..99d72212 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/jwt/JwtUtils.java @@ -0,0 +1,49 @@ +package cn.g3soft.utils.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Date; + +public class JwtUtils { + // 秘钥 + private static final String SECRET_KEY = "G3_SECRET_KEY"; + // 过期时间,1天。 + private static final Integer EXPIRE_TIME = 1; + + // 创建 Token + public static String createToken(JwtPayload jwtPayload) { + long nowMillis = System.currentTimeMillis(); + Date now = new Date(nowMillis); + + // 设置过期时间 + Date exp = new Date(nowMillis + EXPIRE_TIME * 24L * 60L * 60L * 1000L); + return Jwts.builder() + .setSubject("jwtToken") + .setIssuedAt(now) + .setExpiration(exp) + .claim("userId", jwtPayload.getUserId()) + .claim("orgId", jwtPayload.getOrgId()) + .signWith(SignatureAlgorithm.HS256, SECRET_KEY) + .compact(); + } + + // 解析 Token + public static JwtPayload parseToken(String token){ + try { + Claims claims = Jwts.parser() + .setSigningKey(SECRET_KEY) + .parseClaimsJws(token) + .getBody(); + + JwtPayload jwtPayload = new JwtPayload(); + jwtPayload.setUserId(claims.get("userId", String.class)); + jwtPayload.setOrgId(claims.get("orgId", String.class)); + + return jwtPayload; + } catch (Exception e) { + return null; + } + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IIdGenerator.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IIdGenerator.java new file mode 100644 index 00000000..c79d0c6f --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IIdGenerator.java @@ -0,0 +1,5 @@ +package cn.g3soft.utils.uniqueId.contract; + +public interface IIdGenerator { + long newLong() throws IdGeneratorException; +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/ISnowWorker.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/ISnowWorker.java new file mode 100644 index 00000000..5c3ab840 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/ISnowWorker.java @@ -0,0 +1,5 @@ +package cn.g3soft.utils.uniqueId.contract; + +public interface ISnowWorker { + long nextId() throws IdGeneratorException; +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorException.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorException.java new file mode 100644 index 00000000..3c759a92 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorException.java @@ -0,0 +1,25 @@ +package cn.g3soft.utils.uniqueId.contract; + +public class IdGeneratorException extends RuntimeException { + + public IdGeneratorException() { + super(); + } + + public IdGeneratorException(String message) { + super(message); + } + + public IdGeneratorException(Throwable cause) { + super(cause); + } + + public IdGeneratorException(String message, Throwable cause) { + super(message, cause); + } + + public IdGeneratorException(String msgFormat, Object... args) { + super(String.format(msgFormat, args)); + } + +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorOptions.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorOptions.java new file mode 100644 index 00000000..bd49f4ec --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/IdGeneratorOptions.java @@ -0,0 +1,60 @@ +package cn.g3soft.utils.uniqueId.contract; + +public class IdGeneratorOptions { + + /** + * 雪花计算方法 + * (1-漂移算法|2-传统算法),默认1 + */ + public short Method = 1; + + /** + * 基础时间(ms单位) + * 不能超过当前系统时间 + */ + public long BaseTime = 1582136402000L; + + /** + * 机器码 + * 必须由外部设定,最大值 2^WorkerIdBitLength-1 + */ + public short WorkerId = 0; + + /** + * 机器码位长 + * 默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22) + */ + public byte WorkerIdBitLength = 6; + + /** + * 序列数位长 + * 默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22) + */ + public byte SeqBitLength = 6; + + /** + * 最大序列数(含) + * 设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1]) + */ + public short MaxSeqNumber = 0; + + /** + * 最小序列数(含) + * 默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号是0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位 + */ + public short MinSeqNumber = 5; + + /** + * 最大漂移次数(含) + * 默认2000,推荐范围500-10000(与计算能力有关) + */ + public short TopOverCostCount = 2000; + + public IdGeneratorOptions() { + + } + + public IdGeneratorOptions(short workerId) { + WorkerId = workerId; + } +} \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/OverCostActionArg.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/OverCostActionArg.java new file mode 100644 index 00000000..52f8928a --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/contract/OverCostActionArg.java @@ -0,0 +1,45 @@ +package cn.g3soft.utils.uniqueId.contract; +/** + * Id生成时回调参数 + */ +public class OverCostActionArg { + /** + * 事件类型 + * 1-开始,2-结束,8-漂移 + */ + public int ActionType = 0; + + /** + * 时间戳 + */ + public long TimeTick = 0; + + /** + * 机器码 + */ + public short WorkerId = 0; + + /** + * + */ + public int OverCostCountInOneTerm = 0; + + /** + * 漂移期间生产ID个数 + */ + public int GenCountInOneTerm = 0; + + /** + * 漂移周期 + */ + public int TermIndex = 0; + + public OverCostActionArg(short workerId, long timeTick, int actionType, int overCostCountInOneTerm, int genCountWhenOverCost, int index) { + ActionType = actionType; + TimeTick = timeTick; + WorkerId = workerId; + OverCostCountInOneTerm = overCostCountInOneTerm; + GenCountInOneTerm = genCountWhenOverCost; + TermIndex = index; + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM1.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM1.java new file mode 100644 index 00000000..a97c10e2 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM1.java @@ -0,0 +1,230 @@ +package cn.g3soft.utils.uniqueId.core; + + +import cn.g3soft.utils.uniqueId.contract.ISnowWorker; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorException; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions; +import cn.g3soft.utils.uniqueId.contract.OverCostActionArg; + +public class SnowWorkerM1 implements ISnowWorker { + /** + * 基础时间 + */ + protected final long BaseTime; + + /** + * 机器码 + */ + protected final short WorkerId; + + /** + * 机器码位长 + */ + protected final byte WorkerIdBitLength; + + /** + * 自增序列数位长 + */ + protected final byte SeqBitLength; + + /** + * 最大序列数(含) + */ + protected final int MaxSeqNumber; + + /** + * 最小序列数(含) + */ + protected final short MinSeqNumber; + + /** + * 最大漂移次数(含) + */ + protected final int TopOverCostCount; + + protected final byte _TimestampShift; + protected final static byte[] _SyncLock = new byte[0]; + + protected short _CurrentSeqNumber; + protected long _LastTimeTick = 0; + protected long _TurnBackTimeTick = 0; + protected byte _TurnBackIndex = 0; + + protected boolean _IsOverCost = false; + protected int _OverCostCountInOneTerm = 0; + protected int _GenCountInOneTerm = 0; + protected int _TermIndex = 0; + + public SnowWorkerM1(IdGeneratorOptions options) { + BaseTime = options.BaseTime != 0 ? options.BaseTime : 1582136402000L; + WorkerIdBitLength = options.WorkerIdBitLength == 0 ? 6 : options.WorkerIdBitLength; + WorkerId = options.WorkerId; + SeqBitLength = options.SeqBitLength == 0 ? 6 : options.SeqBitLength; + MaxSeqNumber = options.MaxSeqNumber <= 0 ? (1 << SeqBitLength) - 1 : options.MaxSeqNumber; + MinSeqNumber = options.MinSeqNumber; + // TopOverCostCount = options.TopOverCostCount == 0 ? 2000 : options.TopOverCostCount; + TopOverCostCount = options.TopOverCostCount; + _TimestampShift = (byte) (WorkerIdBitLength + SeqBitLength); + _CurrentSeqNumber = MinSeqNumber; + } + + private void DoGenIdAction(OverCostActionArg arg) { + + } + + private void BeginOverCostAction(long useTimeTick) { + + } + + private void EndOverCostAction(long useTimeTick) { + // if (_TermIndex > 10000) { + // _TermIndex = 0; + // } + } + + private void BeginTurnBackAction(long useTimeTick) { + + } + + private void EndTurnBackAction(long useTimeTick) { + + } + + private long NextOverCostId() { + long currentTimeTick = GetCurrentTimeTick(); + + if (currentTimeTick > _LastTimeTick) { + EndOverCostAction(currentTimeTick); + + _LastTimeTick = currentTimeTick; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = false; + _OverCostCountInOneTerm = 0; + _GenCountInOneTerm = 0; + + return CalcId(_LastTimeTick); + } + + if (_OverCostCountInOneTerm >= TopOverCostCount) { + EndOverCostAction(currentTimeTick); + + _LastTimeTick = GetNextTimeTick(); + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = false; + _OverCostCountInOneTerm = 0; + _GenCountInOneTerm = 0; + + return CalcId(_LastTimeTick); + } + + if (_CurrentSeqNumber > MaxSeqNumber) { + _LastTimeTick++; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = true; + _OverCostCountInOneTerm++; + _GenCountInOneTerm++; + + return CalcId(_LastTimeTick); + } + + _GenCountInOneTerm++; + return CalcId(_LastTimeTick); + } + + private long NextNormalId() throws IdGeneratorException { + long currentTimeTick = GetCurrentTimeTick(); + + if (currentTimeTick < _LastTimeTick) { + if (_TurnBackTimeTick < 1) { + _TurnBackTimeTick = _LastTimeTick - 1; + _TurnBackIndex++; + // 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序 + // 支持4次回拨次序(避免回拨重叠导致ID重复),可无限次回拨(次序循环使用)。 + if (_TurnBackIndex > 4) { + _TurnBackIndex = 1; + } + //BeginTurnBackAction(_TurnBackTimeTick); + } + + // try { + // Thread.sleep(1); + // } catch (InterruptedException e) { + // e.printStackTrace(); + // } + + return CalcTurnBackId(_TurnBackTimeTick); + } + + // 时间追平时,_TurnBackTimeTick清零 + if (_TurnBackTimeTick > 0) { + EndTurnBackAction(_TurnBackTimeTick); + _TurnBackTimeTick = 0; + } + + if (currentTimeTick > _LastTimeTick) { + _LastTimeTick = currentTimeTick; + _CurrentSeqNumber = MinSeqNumber; + + return CalcId(_LastTimeTick); + } + + if (_CurrentSeqNumber > MaxSeqNumber) { + BeginOverCostAction(currentTimeTick); + + _TermIndex++; + _LastTimeTick++; + _CurrentSeqNumber = MinSeqNumber; + _IsOverCost = true; + _OverCostCountInOneTerm = 1; + _GenCountInOneTerm = 1; + + return CalcId(_LastTimeTick); + } + + return CalcId(_LastTimeTick); + } + + private long CalcId(long useTimeTick) { + long result = ((useTimeTick << _TimestampShift) + + ((long) WorkerId << SeqBitLength) + + (int) _CurrentSeqNumber); + + _CurrentSeqNumber++; + return result; + } + + private long CalcTurnBackId(long useTimeTick) { + long result = ((useTimeTick << _TimestampShift) + + ((long) WorkerId << SeqBitLength) + _TurnBackIndex); + + _TurnBackTimeTick--; + return result; + } + + protected long GetCurrentTimeTick() { + long millis = System.currentTimeMillis(); + return millis - BaseTime; + } + + protected long GetNextTimeTick() { + long tempTimeTicker = GetCurrentTimeTick(); + + while (tempTimeTicker <= _LastTimeTick) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + tempTimeTicker = GetCurrentTimeTick(); + } + + return tempTimeTicker; + } + + @Override + public long nextId() { + synchronized (_SyncLock) { + return _IsOverCost ? NextOverCostId() : NextNormalId(); + } + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM2.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM2.java new file mode 100644 index 00000000..11cb31d1 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/core/SnowWorkerM2.java @@ -0,0 +1,37 @@ +package cn.g3soft.utils.uniqueId.core; + + +import cn.g3soft.utils.uniqueId.contract.IdGeneratorException; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions; + +public class SnowWorkerM2 extends SnowWorkerM1 { + public SnowWorkerM2(IdGeneratorOptions options) { + super(options); + } + + @Override + public long nextId() { + synchronized (_SyncLock) { + long currentTimeTick = GetCurrentTimeTick(); + + if (_LastTimeTick == currentTimeTick) { + if (_CurrentSeqNumber++ > MaxSeqNumber) { + _CurrentSeqNumber = MinSeqNumber; + currentTimeTick = GetNextTimeTick(); + } + } else { + _CurrentSeqNumber = MinSeqNumber; + } + + if (currentTimeTick < _LastTimeTick) { + throw new IdGeneratorException("Time error for {0} milliseconds", _LastTimeTick - currentTimeTick); + } + + _LastTimeTick = currentTimeTick; + long result = ((currentTimeTick << _TimestampShift) + ((long) WorkerId << SeqBitLength) + (int) _CurrentSeqNumber); + + return result; + } + + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/DefaultIdGenerator.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/DefaultIdGenerator.java new file mode 100644 index 00000000..f44f1591 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/DefaultIdGenerator.java @@ -0,0 +1,90 @@ +package cn.g3soft.utils.uniqueId.idgen; + + +import cn.g3soft.utils.uniqueId.contract.IIdGenerator; +import cn.g3soft.utils.uniqueId.contract.ISnowWorker; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorException; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions; +import cn.g3soft.utils.uniqueId.core.SnowWorkerM1; +import cn.g3soft.utils.uniqueId.core.SnowWorkerM2; + +public class DefaultIdGenerator implements IIdGenerator { + + private static ISnowWorker _SnowWorker = null; + + public DefaultIdGenerator(IdGeneratorOptions options) throws IdGeneratorException { + if (options == null) { + throw new IdGeneratorException("options error."); + } + + // 1.BaseTime + if (options.BaseTime < 315504000000L || options.BaseTime > System.currentTimeMillis()) { + throw new IdGeneratorException("BaseTime error."); + } + + // 2.WorkerIdBitLength + if (options.WorkerIdBitLength <= 0) { + throw new IdGeneratorException("WorkerIdBitLength error.(range:[1, 21])"); + } + if (options.WorkerIdBitLength + options.SeqBitLength > 22) { + throw new IdGeneratorException("error:WorkerIdBitLength + SeqBitLength <= 22"); + } + + // 3.WorkerId + int maxWorkerIdNumber = (1 << options.WorkerIdBitLength) - 1; + if (maxWorkerIdNumber == 0) { + maxWorkerIdNumber = 63; + } + if (options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber) { + throw new IdGeneratorException( + "WorkerId error. (range:[0, " + (maxWorkerIdNumber > 0 ? maxWorkerIdNumber : 63) + "]"); + } + + // 4.SeqBitLength + if (options.SeqBitLength < 2 || options.SeqBitLength > 21) { + throw new IdGeneratorException("SeqBitLength error. (range:[2, 21])"); + } + + // 5.MaxSeqNumber + int maxSeqNumber = (1 << options.SeqBitLength) - 1; + if (maxSeqNumber == 0) { + maxSeqNumber = 63; + } + if (options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber) { + throw new IdGeneratorException("MaxSeqNumber error. (range:[1, " + maxSeqNumber + "]"); + } + + // 6.MinSeqNumber + if (options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber) { + throw new IdGeneratorException("MinSeqNumber error. (range:[5, " + maxSeqNumber + "]"); + } + + // 7.TopOverCostCount + if (options.TopOverCostCount < 0 || options.TopOverCostCount > 10000) { + throw new IdGeneratorException("TopOverCostCount error. (range:[0, 10000]"); + } + + switch (options.Method) { + case 2: + _SnowWorker = new SnowWorkerM2(options); + break; + case 1: + default: + _SnowWorker = new SnowWorkerM1(options); + break; + } + + if (options.Method == 1) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public long newLong() { + return _SnowWorker.nextId(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/UniqueId.java b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/UniqueId.java new file mode 100644 index 00000000..3a8a516d --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/java/cn/g3soft/utils/uniqueId/idgen/UniqueId.java @@ -0,0 +1,33 @@ +package cn.g3soft.utils.uniqueId.idgen; + + +import cn.g3soft.utils.uniqueId.contract.IIdGenerator; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorException; +import cn.g3soft.utils.uniqueId.contract.IdGeneratorOptions; + +public class UniqueId { + private static IIdGenerator idGenInstance = null; + + /** + * 设置参数,建议程序初始化时执行一次 + */ + public static void setIdGenerator(IdGeneratorOptions options) throws IdGeneratorException { + idGenInstance = new DefaultIdGenerator(options); + } + + /** + * 生成新的Id + * 调用本方法前,请确保调用了 SetIdGenerator 方法做初始化。 + * + */ + public static long nextId() throws IdGeneratorException { + // if (idGenInstance == null) { + // idGenInstance = new DefaultIdGenerator(new IdGeneratorOptions((short) 1)); + // } + + if (idGenInstance == null) + throw new IdGeneratorException("Please initialize Yitter.IdGeneratorOptions first."); + + return idGenInstance.newLong(); + } +} diff --git a/code/container-ocr/ocr-springboot/src/main/resources/application.yml b/code/container-ocr/ocr-springboot/src/main/resources/application.yml new file mode 100644 index 00000000..b95601e3 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/resources/application.yml @@ -0,0 +1,16 @@ +server: + servlet: + context-path: /api # 应用的访问路径 + tomcat: + uri-encoding: UTF-8 # URI编码 + accept-count: 1000 # 连接数满后的排队数 + threads: + max: 800 # 最大线程数 + min-spare: 100 # 初始化的线程数 + port: 8084 + +spring: + servlet: + multipart: + max-file-size: -1 + max-request-size: -1 \ 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 new file mode 100644 index 00000000..1b7e90d0 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties @@ -0,0 +1,4 @@ +url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3HY2025 +username=sa +password=zhang520.. +driver=com.microsoft.sqlserver.jdbc.SQLServerDriver \ No newline at end of file diff --git a/code/container-ocr/ocr-springboot/src/main/resources/logback.xml b/code/container-ocr/ocr-springboot/src/main/resources/logback.xml new file mode 100644 index 00000000..05e0bf87 --- /dev/null +++ b/code/container-ocr/ocr-springboot/src/main/resources/logback.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/container-ocr/ocr-uniapp/8B457A96D0F016EAB6EB6D24AD70406C.jpg b/code/container-ocr/ocr-uniapp/8B457A96D0F016EAB6EB6D24AD70406C.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60cb62809c9d98f22beaad0ff07cdf907076f089 GIT binary patch literal 8739 zcmbVxXH-)`*KRQM-jS|=6lo&Tq{K!O0TD1XL8S?y7wH}8MLL^^~X znlwo$A&`)K@qO32_s{)t@13kOD_LjGnf=V3=h=Hth;zhM0P|xlT`d3^82~^=`T&Rv zfQNu9WdGW~{>r~b4*b_skdp(+DJdu^|8r5%P*YOTP*GA+(^1pV{%fRf^mMfJ|9<>? z$$uZcLIDI)&{9!S{b$JkQ%CFquuuaO$xMJ`LVznQWIz@&Vjn<|WGBUc%p)25-$r%? zNKQdXMKXhqG@yZ*WIhl`GMa*9F==!N={|s*g@X0E+$KNkhxYb(NdvhNzgh#LYVjib~2VcOO2|)Y8_`eQa!E`W$R#ZeeR@@8I~x z$=Tb-_pP6QKwwn#yO`Lx_=NO~4<9o>ea_1Mo?lS-qo}x~w6?Ckp|PpCrM0KGuYUkO zI5a#lIW;{qJ2#J5L#}UZZf*bB*+m~6pPXXOu;&;5aFGFk|Aj^B|Btv>NVu;2^8wXA zTx3`LNEeWWoZ`A1CF^}7s#l(DLboHR*&n2RtLdf@mVbugc6pL(KR?YB60KnrM+|kyDO8Q*%sk9a*XTtW-q)Li73-|INR-l^O9#?zPWFm8f;x z2VizpM8IpcPW?y!)yX`qHgcat-ow9+s(;NgHP63x zYdB*7f|2hh&|GF{T-|%F!Hd?8ZdZa7J2SVHok#Y)uli^fz~wISZ-R z_V&S3KDJ2Z?)`wOG9q9#O@j*+Q-lUuOmF?JsI;rDeO}X=r#>kmjMZHYRp7#NUYXfZ z3t1~0;f?=hI_|)#Ewhq)p1bWTCOVo=!L}Jx_Qoz;&*dRs@KSXc&jePxU@?_bswu^Y z=Sj&g|500Qg%M3*cA%62*{b&o!yYGIbPtluO9Y!_FNO$_3Uz3~opJ;6^#*lGofb=l=2 zcC~UU89|V3F#J?Y{Uqkmd{JA*MAt?_VoVpqvLA7uKar#YOl9b z1zWMMF1!Zo9i=1)@6U7Olit&8tG^2p!?Ogf{>UlD_L7G1ok|9z>nn$HN>3`;^9GtBmNTxDNtZ=RnDq1hI+hI}UiB4sQV z;7BMto$^+%r9_$^)915pb;(lFjZ^{x#!Un`rY6i-(_tNp=Mi&@PC}4(SrKl^E_vGu zTAke^&vh%5HqGf2uK0y|tj&#JE>j^V6l=mLm-q5}3-m7p-fof=L zfzTAP?2AyQ#dxS2Ry71x!37cUb=d>9qIv%yq;`URj{XGyoYV88B)#i@3>a@Q*^YuR zvRIX`_(1H^H-E8#+m`o#bj-I-i!`6>ni&L$;pQ$O25%h##rADT>>EQD9dNJmKSYA# z-9%7-qa@?%mC_XsAb+;-`>WX~A|M$?U#`ItY>ie5m-dZ!UmbUMj8UDR za?gP9wZOS0W0cqPBr#2E+sSI&$mE+ZKx?Ix<0p1ObYuWJ^2g9?@qum#0B>0fTr*<7F-p z@Wv(rL`DSIexV#QMPJ5srs}seM-Fq>Ni=7wmDoQuBtKE;3F{UOl?Z->{ZipS@)RAL z_+x5AuSKlMvd8M?@MbO5Qx#-JhW$SoT?<19uM(Oybd!K5 zMv81|HG;9$xwF&I+F3lSa1Y@Q*3lg;jQKu$=t2ZEy3G9LzcOsZgIM@VsbU{|FhXzd z-KJg&!b(skxrMpQ0^f+vFq|=)!DZOqNxZePHw1Y^O)gY%oXX7GvcfaWF#GBIbL z6lYoA)M$NrUQ2Pr!FR=k@t*RIxq*QyuNJLX+TIH4an(H7=C|XN8viSm4$g>gxx1F@ z-~rDD-LBDKs�@vM@>;jvI@3u2|t+DpT;@X_&d5wSIJ*2w0*jsTw!L-k>ZDLdr%4 zR#uc+8~Sc8)f*XliXu(;fMe>C+n{6(anwo#NyCb7`$xe8GrIGgW>KTQYEN$+p;`>& zG7pBMG;gN6cqL?RH{;bvZhRd&vO-4S))8{>_3>(?0?pfa_v6}S~rbdO{zCS@= z!i$GI*d3}8ZksyQ3NK#CJx~o7ap9|^xjb7)f7VY%nO_OLtTmt2xV1w_jJnLRG{qu4~2 zJ2}^T5uFF&;conU5<_rX75x$KKUKU-ySg}rKd|}i_B1WTho}1+l)RSw>iJ)M$t%X)o+mC!oosmzf!i(xh+?e^~!4{ZN#mXi|4F4nVS=fz1R>m)0)_U?$_P2 zRellCFV6JdH+lr9?x_Gl?}8!gRk1V6RF_r0g0%jDik1-bS6^(Guaw;hxaQE2L!i0- z@_n$vY^811u$69{X~b>^7*%x(J6UPD3vB|$RU8xwHYc!L2cU741j3TDzCDNZSR6ss zKgBFma?j&F-EDaBuNLZnzH)W(;?6sZ^1)B0F}i7(`GBJ_P`kUcxY+!oUA$b`ZYaP1 z!ULhD?^2Iy7Vp+bpT)r+<5CIKB^7%-Met;wJ&TW{Q=A0ePcDAo31x;YkGZPo{l}I~ z>4(F?MZ1y*D_9}@&3YK0n$Z>APXhd}wI+!Gb;I01A1DHsp+PYm#6zfS1$beI>IooW`YG#!Dc(vhK^UKWca;Bp4W); z3Y}1BQRqvLjt$o5`RL@=8t}8Xo^-=ILYhbd?^1izDGhn{tNFN`&0SrG?_|e@y&EF5 zq|JA0(B!K`v)uul`>d(~w3?!Y?o{^ALuir-kFN!}RBt9E_mV<|?hyR-Key_U=$uh2 zdmxB<{WA0+lJceJ!mmcquoAwbKadDGIHiZ8-~xAeL(gJo`AC8%IS5)Uywyhdy+ZNo ze&*c;fII=gv@H}GIzZZ1v{x8l`>yLWH&(-XH1gF079~LtVa1NT;&WpH^-q{1+Am&Z zaeS_CLW012uV0(Q{h($rws#E|Krmy9{qZ_5eBqo!3D44E=0DpXAQ+|}<=DyYP(t$E zi$WW$1>(jB+aSHKzg91RqRwkdVkJ*=GN#T|N8_KHlCOIXkX2T#;-4TUc23n2=f*}QT_8K{!<`(~zHD^Mcq!&FZCcH#C_U3L zeO>TzQ1*KO+s_88Iv34Sji~1?h8Fb{C)c|x_a=2X-)2`ZG{VA0W%>u7u&zXlg@VE=vWZ*&J%`u#>BT9 zm{LRv@oR=L`!d08OK(-b=u20;$YiIdm1MqkOBwD=n!q(kTCdNhW;@+ucSljMF?$KaU7$S#Zzn~6RW#fYFhyeCrbN7OH;S}Zm z->>y#DLlUBrapE*FvNK}ld^0gzy(C3jk4{ZZML&p^BZ2Y74pbnul14i*_c{-5dCRY>WK|AHnE2b zZaRKwvFxCg0*c;6}myrm-Zk|}PgT2Z% zEwdK5HJRWkgP=~8~_8I|k9t#BTIyBq9-_S?Ti%gDy%@lRMj z>#U7-jnrhUcH=wwCAa9yzjrc;p}DM5GsIc`LG1e1_Fr!qPxw;o@3C}G;{yk`a@__D zA-n!DcEAQJeo)zD|Ko94k+tQ#neJ^4d6$rwC%4EQxwknmV|aGTc?Fw!rGAT$w)2U_ zon`%2F{!_T%5WoiMX))E=@Jw5htVVfafn?PWY~#!y{=P5FOiyaR;a7Ia~!F1akAA} zjP?7MO+9Zgfyp3B5lh@X~dG1PFLwcv>cFM zYeMp57UPLMUj?NQxI1Pv*#CI;ez&v=$$4VS+1Kpi7`k}nw#o)$!?`cXBC3|mZA>@1 zzA?g{iz}fT?s_aQSXXXFt?^PRQiZ=mrKGUs+Z>@gtlDa&e(OundMY=zQC(28y4lhg z;?BmIm9MHp4&0e745`BDK{!|HKJ8Z&2ZOMBYde3F^FByb*x3#yUDFEArC+t1-L{Di z6$|}MV$4aR|DAZ>5H8&uW7V}h%4Elo#noJokU;RCvzXJMl{jeajmk2l_;X0&D5aB3 zwiRpYrX%;^m}mB4JSjavUmkLM{A}x0B#%vo&r+pfC+4jle|JHoew-7~uHgBAL2LU@ zHZ$X#06xEFwXmn}uVwS;aP7eRmU+ zK4HUsE!8I|WrnIj*OndhCWSX6$%xjs~m4KQQ>lDFGn#`M>fku&xM^~BO@Yh?)bol z18<5tF%BJt(VSSu^)8Ge>CCHj&X3p+`a_^Nm-1GzeO08gPk;K-I%=uiqo;xh5LlEj zebJdei7sa#)zc4qX;70r%N($p%e?YC3#Pa9;$B)szu#uvzN(_OUJ?5CjfHId2?$Zw zSB-xJJ8n=prD|<&;ftj&AJ&o&_s;d@-k`nkBhcuVDb}Gc|5X8-Ti2w#P5~odOvV(2~!i9wMRG*rox%x}2aY)7zZ;meBzZ+7W=H~{O!5vKp z{G%y1(3-+j!ZP3o=2^bepBFWn0AEG9QTc86pN2o>mpfNTf-0#^Zd0!mC)DUt=Wl$L z&(G^Wwr9iYl+n)tKjh`j5l+BC2-I=o`Z!JhQn8QZMZNvP^j&tJ2D#N3|7Au4frfZa zY=M9%HZaArdU<$xFiI*6py?Ra%`%;VG)&=$q1~wlo^} z5s8*i1>PAY7$LOeU*Poc`w2Y+qluf7_zt^cS_O~s9uq|wy#$`|8&NeapK?Z!wD zBi;>VjI}k%q%qb`-&~Kmob)O2VDW0%-aM(QwP$vQ)mkZm7wprwd*ttwZ2Mt2D29#n6jdyJ_py&OECjp_o&pJv}mPZ5aHuljQV9#uoKR@ z-!CNJIqLDGl=;J5sgb^)ZIQ0O4&hhNs=kM{Qa*7%T3dmK zb&QPaH269TL6l|{`%Wg5_Oye|q`fHywfi(xqVVaykz&n4?&T_E2TBz>b^&@vP}>s} z6lxY)e?E~;2fgDG&q5{jYt9t7IQhY(i3uXSm5P;c-ESlU>YKW6WGm4VuDRU&=6>$# zV)z7;Oa#b!S})+j;(0Oh=s>F^$HzM1W2%W)C^ew(f|GG!q0<5s=+uL(H8B+ZXo&mSWFH5!qF|v=AZ?D>$c@;!T9`FVo0tk~J@*CryOk<8FP3jWp8qGEAUxvHg z3>aH8-v9pS$7ahB2+uM@gEuSbPCm-m>Df8frY{kR$NKD5=~T8WPX<>b}DHG0utb zJ6rbPJ~|(aeYch)AypsnDrG^p$tC8^x-#2wWZM)a!b8jBUeQ(YXglPXNPU`YI3`Fh5yn!k?=k;N zN8_VHC)(&TW z3ch`r7kcZ3{~0o-;I~DhAMVb~pTFJE;+`woHjh#9-!fH_44PfxNmG8AJ(3QrBW^VN zCK(st685aI1G~_#W+aL{2H7uJTGTfErudt$usPh#l4JO~DQZ?*4T?!(uEiCdD^590aStpu}HC4cehjJ{Y?JXtP?tO4>)4X zE|@ApNx`)HL6|c%yp}L>vC=!n82n)JR6WRSl5<&AQp<5@kw&#^uHAct)>NYZE2dV> z5JPvF-N{dwhqB>D5a7(B9{kf*N5XNn0&-i9b)%COq2hfzd8lVJp20(Eu| zqJMX5Yv4Yo{MOw?E|*Ypz8NJv+X$u(1>0Y#wDI@upZ%>}WK(+6Q1TQA2l;()YUJ7X4enC9Cu%fdHpbj`s_bvtNBC#$_QeJcuP%S3cgjJEl6fr-7>Af zO=fxdcDwYw4)1m2!ROs@jw9cKpk;~5N=|ddD5=r{Dz^>bm}q^Gfkc`_i(4p(gxzu@ zV>tdlU{=w={{D4t^@y8>+csG%4KX>8Qo=;1;>41h$r!S1@H)hi)++O8LFD8-f^T-7 zBt3=)r#Si0F<9+Z4(iw(&C)tu;o`QHJ<4j87|-jGdj0LGMl(O!h6!(gO%D!0%k~K} zOZ!xbLoB8cAB?!jd+a|J%;(|!Y)NTv*@tH$mpWJ#-0ff*)Y$%r8-X6{Tc}$KirLJB zv<*H=UmBBAS=1RD&45+}aN$`<8T?pEFojYd%Q}eu#ZC!d&47%|0pe<*>s;RxENcjR zh8eVu3eTas1`F5Vamu5?))hH>HbSq)StA|@ZkW<4Kh<3Y8>1x&bT?2%k5Od{!5pX; zn6lFP+c(Oyhh6KUtIzFO-t+$C5A-bt4u`Gg(-dNf00J#jzCWYx)5{FQ>V~OxDrnxu zpshx7^~Qyd8rnuLbk?SJEW#aTcWKxSrNT26_4*^!p0`5aMNx{v!hl>ab8WF1*ey%= zvg%Cd?IpYeS}87@2(a#$Uh8b{c#AtC`_!ct(Vi!{zY-&e0ypE8);{};2?XD5hqWi0 zK53kli+B$Z%h)p(FL5&=X+|(9VnOf{XuqDZaM--{UDRUsi-Um@>7|CccS!PwpL#NC zv|r9)s?x(8kJp&u!cfmHgKVaJ5+$*2zIr1;=3=~Q*%FJ6R6Agh(+XA++6RhXrfEkp z^@LGquGN%ETJGL$m>zOMEc}?mI6A$;?`^tAhp{WTzq3}I#J^Z8$eVDLd3XZ`Rr-9y zHuU_ZfnkZdzIgW|QouQJB+%NRgpphWVf5I*g|(tsyVlH!b8Ab8k4}bLU~<1dX79y^ zx3f0Xq%+Gj`*riz4N$}1qLzaqAMeE{nbExW7|pvyBh=|!LxKp$R_RaV*SBD8$TIr57(4A$>#uq-voId3Ip_iZQCRqBD6dF+ev&1}>!QFm_wskNh}cM&kAjCF!v% zo%1CNa8FkjI<-7V$<>>@xqO$-{uLFjzy4T43*`zD$QlZzsCXOmLUwG^L=%4{ZTi_N z9~v0xWIUr(oY7LUiq|?IWR(y8h>`q*yv_RTRYuez8vL>~^-aQN9If1$Zb4mRwZ49( z+lq^p$;$=Z^ZO?pV_-Xop7-ubJmOt@7@Y>Qdka!>LG97~&1p6eq4VRJ?0`G968tb? z)|z6xp)Y3LbTXgP{yf_5TGv^Bu!JWXjIqQ+Q#d9+~;zymHFYVSVC4t_5nC7`KexO=#ck)96Y!osm z;Kjp5XQkmDg?PZ=Yj})CSH^)2{~LnRc&BXW%(5ajwuq^I9_Z2PyQ3O9P5OD(A! z86Z_z@B@A1h`wd7#aaIX=G_6i)k4q0>ufQ;65zM)?+JjT7pxM}6?UHt-^0m8ij(6- z$hrX8yLcwFDiRhW#}(Ci-I>P@wD-XBXV9@GJDFHYrwm>nUGQXC9-CP1{Iop!oXzS> z`T0g#UerHN&-%Ur%;PDYU=EVjaK zWd0m$kgL=;W`WIrIpJ^Mq_#f#Rhb9~pB+xVT0foj;YdUUl*|t1WWr8Jveim0)&v!L zPaQeOl>BIDYZ%I{ekm*9R;_4D`Z8z2 zvW*0r(7hT;krYOn?+Lz&WHWao^&lJdF(D&Iym8D2{IMJwgS}eEmR$*@KHJ7`SnfBP44v# zL#{-CB2x$ZQ*uNi*PFSsr_NA3tO`e3Ii_|ZAa$H@YBeQ2E>(f#7x5Uyj%zv@dPq2D z%E2YS{wdeq(<`#jzRV#V6^$ML!v)AY7s^5&-ADBKoPA$w>f@Y0<&p_Jbk#WaSYwx|*=V0bBD*&aInx-0ng989K*aN^U0V)6y zAt8v6fCvNv5fc-UkkOEnk&==zQs1VaVP#@xV`XAt;ouhH<=_zTfRq0w_=6PuT|cJ>aAPF~(VuYLXe10vo;zKx207n7Xw{zGco$4}{b`2~eV#U-U> zUu)~?8ycIMTfTMo^!D`+3=U0BP0!5E%`YsjuB~ru{@&U~{5d*4IXyeSxV*al3l|Q6 z_rI{P=l=!vf8e6R;=;wp$HNExg$oDQ7b|#F_yl)E2&t7IAS;jCoT6bwG|EZ2HC@D9 zVtR+P)}9k2bll>rJV$>)`v~CC)02v+*)_HhT02qjKIPZl0(!PQn zmZ~MWN8Aj8MbNaxHZG069zFd|H5(}YiWfqie56d7xB4Ycpr+Nx@2zY>;<1*{SxxLq z#X>N*TfW0%O&EaWl&c7FwC)XwyUsLVHg%SbH?h20wZ5Ra$s==P?E- z!vOuT%SjAiw}=78gpqz2Al}zg-IlaWMvXBFkJdu>OSq||w<t2iBP(N{-`T`QdotNy- zdvS+EGdhMcJTk9r3cUlSwcIKxal(->6L?8pf8h|7qHZQ2qaUbw0S&|e4{G{&G6gyX z+g@gM5ooyN$HkuyG;h79t))T(d>XL*nusmcUmx?a?_7*EY?gP(|`tjv^ zbR*A+FW^iiF{(es@bscsL)_P=zhQ199XctH(++fw$U=c>jhi5_M$BHQ>np`*+>-VImD6{w3 zHx=3J*T5et`vT#o6`XjDgpEjTM%x%jeA#XX5(8Y9XTu`&13p4mb1nGf9U~k0*pO2N zTh&^Mcz}GVSm0;YVX4D7S(dOiGH7;g)mXO)YgSrb>{2ATcS5qns@ zQj)Uiwf&uSu%3(y7(zpf*rrXWdU*2(;Q!u6T7M#cofSH`4(%>gY?ie710t3*S z-&y_q`b5dqbNz8c!j4|LcA&Ec_kc0Czs448=pd56;)pj^&T+N;fMVfCZ;rN5jbDRh zUyAFycX-l*16xalUTQ4IDXs_0gVbXx$5k0oEe32gsu36fM#q5UyAg+<+<0JspneR{ zGsla{U%R28M^HNTYrC=?4&nrgzxwt18=}=iia`wzl>FnoB3{Mth=!eB04bD?9JUFx zyl@(Jy>4O!TTPR|BTEtP6~zcT)c=v@|W+l_(tDVZ?KS*^=*jE$9u)>)#$6TW7x)=e}!h4 z@R6;2X4ur)^d-jF#Po`!R?f0!DHBT7&*Gu}rFbR;`kP(``fsHUH#8Q@Slc__f&1E8 zyD`9G13S~1FvYUdb^J{7BIyoK;FfK?C{1}OyU4IFH<>RlO!bJidhwLMp8lcXXp;Cl zp1Kj{wJWyYw`R^BYXA~YBBZm;56j5DF#@sLDPO-)wXLq z9Ep&hnVGItVwu$T5!2}XMCdOM`m)yGa83jN5IlVLSV+&*&vH8N*eXGOeDHG&q5o5@ zR)fte_q}=4WI`wgsC%6mq6NKQqZOy`pKLgHszSn-*GOsbGW;HIljx%a->_6&Xr$AK z3jOWT;!?|QL@4;9IHS(pb?A=b4NoKTrWYFtBm|W;2z+%kz@Kq6+UQ%1uvl1Bq1emV zNK{-t`6gjDC~^*&nu$p`8$^#?=X6{KxrVyV`U)kB`PkbDCwdKZ3$Le>oWDXFTnHb_ ztL&yOpB&Jm4*CAp|2u@|PDZEi?CMX23Bme~4JoS*zbR}$;#Rq%Y1H3Bx9?SBfE5R9 z(C|^%Q*2szVgRnmjzL!ppl}3T#)W+~g^smZTy$1F`({C22PORT%mQ&?^8Q4d_)TnU z7-u9m8zBhHSCxk*Ess@87(HBktkLsLr3C&-6(*Y8A_AsmELm+D_jbap0qx4e>36T zS|UU(UCFt&h+qz9yVDokM`2+N{A=(`{~kQSe>rD##;2?BQu*LTvOsXwU{##@^xZvp zHf>*!!uZOOd!ztTb8S9<)`$P>b?+MSowxGy1{$z|Ha=<6tf%6j+Nk5|W@yrO_R9tg zknIOQ2vc7Wgm3*4zF9G%T6^Kf4qV16elW-%KchpBrY{O{HHMJsae6~8B5>m3Sf~NP zOE?A~#=8`ik^|fOygo5kOY<-Zf9dd`H;N@xLsw(>smu63hgjX~+|>PQ^}jto+HyD> z3(fm1w^r0Nj{_sTq^ew0kM8j9VOZZnjOsk(w8 z(-2j{TvZxfoPP=^ii_r^EkvSuwa>0CxF?bFOk%8OO=G6-RVP*Yufqj)=l9SW3+|aZ z+teTpjpW-Y;}->olL1d!{4nHTiCH{651He`057dS z($!~QqQ7DQ=ygnU8SL`I7<%*BEOh;R09tS4<0jq=|B<&Q8YJ8E6-B_t0Y*R~rmXV2 z;XAe%z+2B50~{Skpdu7m+;Y^HY?0u^V#oE1d{!rUr3Lp5-DVIYIJDIJ*InF>BG{qL zb{LayCn~Ak!)!8{1?61c_M)Mxzp2X7<%j=)iOQ>&4c!mFP+z^lqIEG^n>Do8E=}J-A1$RTPy4g$jo=*28wd^o}XYMQyPvxqj zze@v1tELq9+N=!36QExk_x5M}*Zp3IlLVp9(l< zPD@Vcbu3nzvFJmT(_XOkrej4Whh!gV?!w+ ziap+`x7=iq=;X;QQ6-v>1E&Q1)(iN!{frf^W?T9hOYB1}&ax%@`F4@7LRW)#(qo0Qx)<>&>J%cNu7=6#^P%f_?XJJR)J~=I>1PT}m~D8yFy$j{r)*S~nbU}~ox%yx ze3owMKNrnRhxNf61R3uY^K_)wJA{+NaVKkvV#7H-{wumKU0&efVUJBpgxW7D@pG7P zqEw;oLm=3}UQ0$$gQ$(SSW51|3>L(GM7z8_A~+jKlNMddETj>#vtwp(%CT+o_@(XR zvXJ^kzZ4Nat&O|-(fT;3#iaRUDo290*dhU^2Y#%uO8;rd2z_*w>Xe<{=BIY&l0$(k zy9~JsV(BgUa;7tNj zHqbmBQBP30-u$s)B5@8?L~lW#njN8$IN-v75&w`%48Y5t1D&USiXt(YS6asa)ZJ^Y z0++Jt%jEl&(Ib8Nq>HLdZUi;IBZYci(JINMEVh%XP1#J;$%D1uEv$cSNcd^o@EJ);TDXTK8+E`FV!x7>h27BiqvK3YbxhHUbIWH3yy$S} z(be_yl%|WrOAIg(YaUtA(x2-@ElZ%@sZW7eiMTXEeU&NL+EfAqjOSGwR`VP^qU^tUbSbJIBav)s z{5_!zCrF)xQT|Y>+9Zc)OSG~r-t8TCmu5V-XUs?fjwLak{GrFT^AJ*nu)Nag{A-Zn zmlv|#bk7O~1U8LVlFeh>xVdF=Mio}f{W)7QvZEy>m#JOlXwS8yTAJPr*fY#WIa-QL z9~NhO9K^$K3$|Vw8JT`5nqScQy^mm^^gvz27Gyij|Pk}Zb><>T@V>CFM7=SfMqh&KI zgD`PeKCfn=x6I@ zRjSHXp5~cBf{UG=nP&LA?d5-7<&`DnlR}D_+aJ!nY`LsmM=8&tm5Nra1Qd3Lh)3l; z-eMBB+v#De_!iUoue&EC1@Z@R{xodL>O-@bYTFqxho^p)$hR# zkD+5STP|bQ-0Wu~&=20`OxS6I2!3reG<~^Tj$E+Krat0;Vfmiw?ZWHsGPMKLOf*=!rR!3?Ld@&A*GL@5b~$ zwtRZHrx=hOfV(AIjF@x+KFI1r>DwJ0C6{ey?nXCg=xP@Gd2}}Va1#zxO6}1Kvu(+J zUAfk!7n4Ydyu`oGkc)3Vo}-nErj%}; zJbgUn;~6q3g6cw!h-nfO_c{N0uzzZeeA6hy{I%MtTb@6eo0uUm{ZF;R^II~mznkTL z8{*yc)xBNi47AqSYho?5*n06R#f!XfBH1P^fv6fxkkp0$7Q_)T*-9<7XCgM*yZ>pL z+-~HP^5#3K4TIs*S<;7}(qxS|nAR(gw)!;t7>rcPEli>IZQA$i;+i@OJj@CU7gS5e z1g2nV(u?wrjk5^%^3Eh7nj|D5UFX`kl`E;fb#}^$>GdNRC$%()k@G|NdOb8vGeeHG zsn?@(z0%p4si~dq5l5`Y@WUf%Eb}&qSL81~&iXobG>vgD#cbq~94fW-iD<3CMZ#2D zN8Z|QIP{71&hwO!-(%c4Hi2~4cA+vEf&AWI*vz!yR?_mCoTZl`jGPbItZ6T}0Mxxy zlMRSs&3ER_bq@VnzZhB=-BGQA)X#BU0v!wL9ZmKe-Q~}Yf9OO$OXr76kT>EbIH#Sw zbk3J;K#&sNZLpj>SHn-DbJumiQ&9_BsqKj|h@}SAzclgpa-I~??X1C5==&U4JuIig zA`*S^>UoxjvEfPGZT=!9^G`WK-T@;OjUs)V${O+@+6RQo+C3i$bTa}&Jx8OKWsP7=TnsaWs_Yh4Y%7#Sg6i0TY*{6-9Xl{L&-oYoo?|w;ve#8F(#Kl#p zC(puvL99*;Ge2YCkYe0+T#ZsuE_)~XQ}mu#CF_fwuwn<~sqOkQZtG{YRC=tWW`ksuUdbSdx$O3&Q#}n+e7E3|9{gCc(hc2^M?;$A%m#W;QVLC zQXPoSh3?c@Tr**_7bgAWQzU!^fT+@bAy9@NT?wPSp8lSZP+mT|qDOf_f7ekdz`mE4 z4OjFsL2VZ1%?7;Sdui@O7LQ%yFMLm{2z;^BWVPF`u__$g z$ePuuP@7#T>nDq47DQ|Oke2Ggx34sVk8{uN=GV=-r#o9WH8s}524l8zuf7_eZv>C8 zP-?h-YuL^q?4^0tlk_N-#UU3$W68$~uts{)vs1X!o+;<_Ln`tSomL4wGrSf316X(MaXHDVKSMEy&@VVnd)&0aT$ z0J@CT_WOblHS4vVpC2n%X<7%=-X7$go{gsg^d;L&dfI1{v#q7T*k4D??q@m}-~~Gd zP!^{|OPrvHOY&k!v7A`wzeJ913@x{Z0d~?c!2Rj3?a5s~*A2L@CsczfpT=LqM^fn< zYt4taKZetX63FyJQaeRE>9_uv#PExxT3B@O;&fh<6ngpae9wA0I{K3^5Tc4pB=oY; z^{5$tdeX@#8D#jmCPCc>8_5tg5G#Q;%@pr-v$1+jUJPLD#*-LwY-F{qqngf_W2aWy z^rKqNh5h?fUHy`j{E;f{>7~Vr`na~2$(5H&Ke*ehN^*D z>$Pys9R@l39i34S3q+MG8fSg(&;9yMq@jXEdWG*6Gq87t1;eEU(Mx|^Cb6Ukof)~J zILbX+GGPDn8ySUFsHg5bQ~Hjs)oG!}E95@7FKVvx?XLPgx6V+|NnQ*S`%Gyf2Kc6u z2%Uf)^I`y#bUD>gg<%Yk1ir>{D^X_w!I26aT-mkIji=~(ydHe7b9W@R%9+iJ5nwke zo=@~&nhI9Lk(DEqs6?pxL>+9|N=zX?e z7}hHBs9nc%{$EbDKROR(9?`aZ0Ligj&)`Eg(c%y#kC>RRil+E+x&aVXZ9!a|2p#Ix z=w2HA_gWVB$53ivftB>pKt(P&JMLwr|f>PpH~ZB!rY7^F-1>Oo{lgF8ELR z&((#m!Uky^AUus5!&e!4A6vD+2H-mx7(gN~hA3-z<~DX^qN7=0QNKOl6^WmTbvpjY zqF`Rs`Ap*l&tGGTs&_>VLhoQr)wqK138?#A&?OresCvi9SBQ$;0kMA&+R47!q(`B^ zNFADI3gN^<_Zl!jb)s7{2FQXym#45?{6xzZ+Fuo$XFbILw^eEA2X5dy8;s3vBGiE! za6~V&8+5C};$j<10O>;%J^kTrl0UG2<(|V2Ca}C|$&weA$Far$aCHn2lNW=R=6.9.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.4': resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} @@ -273,6 +277,10 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -324,6 +332,10 @@ packages: resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.27.1': resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} engines: {node: '>=6.9.0'} @@ -348,6 +360,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -365,6 +381,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -434,6 +455,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -498,6 +525,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -835,14 +868,26 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.4': resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1959,6 +2004,9 @@ packages: '@types/node@20.19.18': resolution: {integrity: sha512-KeYVbfnbsBCyKG8e3gmUqAfyZNcoj/qpEbHRkQkfZdKOBrU7QQ+BsTdfqLSWX9/m1ytYreMhpKvp+EZi3UFYAg==} + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} @@ -1971,8 +2019,8 @@ packages: '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@16.0.9': - resolution: {integrity: sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==} + '@types/yargs@16.0.11': + resolution: {integrity: sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g==} '@typescript-eslint/eslint-plugin@8.45.0': resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} @@ -2287,6 +2335,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} @@ -2592,8 +2645,8 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -3286,6 +3339,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-intrinsic@1.3.1: resolution: {integrity: sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==} engines: {node: '>= 0.4'} @@ -3705,8 +3762,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true js-yaml@4.1.0: @@ -3847,6 +3904,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -4050,6 +4110,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@8.0.4: resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} engines: {node: '>=16 || 14 >=14.17'} @@ -4143,8 +4206,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.22: - resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} nypm@0.5.4: resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==} @@ -4591,6 +4654,11 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -4633,8 +4701,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sard-uniapp@1.24.7: - resolution: {integrity: sha512-QXk/zWH5j62VOUsaf8mSk0i8k/OMkUoSIqOTmxb7vpC+UpiJ5Uwypayb9HarlgStXpqIc3HmTOIG+2cNo+yFCA==} + sard-uniapp@1.27.1: + resolution: {integrity: sha512-KrmC+2NoPtV+ugOGGfUzdS3gr650SRzA3rkcs7s8xQ1wHhOmQA2sf5PlUQnIlmt2jope/JE63GpuVVjvy4bx+w==} engines: {HBuilderX: ^3.6.0, uni-app: ^4.11, uni-app-x: ''} peerDependencies: '@dcloudio/types': ^3.4.8 @@ -4668,6 +4736,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -5375,6 +5448,12 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.4': {} '@babel/core@7.28.4': @@ -5405,6 +5484,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.4 @@ -5479,6 +5566,8 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -5508,6 +5597,8 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': @@ -5527,6 +5618,10 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -5569,22 +5664,22 @@ snapshots: '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.4)': dependencies: @@ -5596,6 +5691,11 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -5604,7 +5704,7 @@ snapshots: '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': dependencies: @@ -5614,48 +5714,53 @@ snapshots: '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 @@ -6097,6 +6202,12 @@ snapshots: '@babel/parser': 7.28.4 '@babel/types': 7.28.4 + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@babel/traverse@7.28.4': dependencies: '@babel/code-frame': 7.27.1 @@ -6109,11 +6220,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@0.2.3': {} '@clack/core@0.5.0': @@ -7135,7 +7263,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.14.1 + js-yaml: 3.14.2 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.3': {} @@ -7143,7 +7271,7 @@ snapshots: '@jest/console@27.5.1': dependencies: '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -7156,7 +7284,7 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -7190,14 +7318,14 @@ snapshots: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 jest-mock: 27.5.1 '@jest/fake-timers@27.5.1': dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 20.19.18 + '@types/node': 20.19.37 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -7215,9 +7343,9 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 - collect-v8-coverage: 1.0.2 + collect-v8-coverage: 1.0.3 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 @@ -7249,7 +7377,7 @@ snapshots: '@jest/console': 27.5.1 '@jest/types': 27.5.1 '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 + collect-v8-coverage: 1.0.3 '@jest/test-sequencer@27.5.1': dependencies: @@ -7284,8 +7412,8 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.19.18 - '@types/yargs': 16.0.9 + '@types/node': 20.19.37 + '@types/yargs': 16.0.11 chalk: 4.1.2 '@jimp/bmp@0.10.3(@jimp/custom@0.10.3)': @@ -7777,24 +7905,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.0 '@types/debug@4.1.12': dependencies: @@ -7804,7 +7932,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.19.18 + '@types/node': 20.19.37 '@types/istanbul-lib-coverage@2.0.6': {} @@ -7828,6 +7956,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@20.19.37': + dependencies: + undici-types: 6.21.0 + '@types/prettier@2.7.3': {} '@types/stack-utils@2.0.3': {} @@ -7836,7 +7968,7 @@ snapshots: '@types/yargs-parser@21.0.3': {} - '@types/yargs@16.0.9': + '@types/yargs@16.0.11': dependencies: '@types/yargs-parser': 21.0.3 @@ -8290,6 +8422,8 @@ snapshots: acorn@8.15.0: {} + acorn@8.16.0: {} + address@1.2.2: {} adm-zip@0.5.16: {} @@ -8380,7 +8514,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -8390,8 +8524,8 @@ snapshots: babel-plugin-jest-hoist@27.5.1: dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 @@ -8426,7 +8560,7 @@ snapshots: '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.4) '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) @@ -8639,7 +8773,7 @@ snapshots: co@4.6.0: {} - collect-v8-coverage@1.0.2: {} + collect-v8-coverage@1.0.3: {} color-convert@2.0.1: dependencies: @@ -8849,7 +8983,7 @@ snapshots: es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -9410,6 +9544,19 @@ snapshots: get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-intrinsic@1.3.1: dependencies: async-function: 1.0.0 @@ -9464,7 +9611,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -9635,7 +9782,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.29.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -9672,7 +9819,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -9768,7 +9915,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0 @@ -9783,7 +9930,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -9793,7 +9940,7 @@ snapshots: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.9 - '@types/node': 20.19.18 + '@types/node': 20.19.37 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -9812,7 +9959,7 @@ snapshots: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -9842,7 +9989,7 @@ snapshots: jest-message-util@27.5.1: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@jest/types': 27.5.1 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -9855,7 +10002,7 @@ snapshots: jest-mock@27.5.1: dependencies: '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): optionalDependencies: @@ -9880,7 +10027,7 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1) jest-util: 27.5.1 jest-validate: 27.5.1 - resolve: 1.22.10 + resolve: 1.22.11 resolve.exports: 1.1.1 slash: 3.0.0 @@ -9891,7 +10038,7 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.11 @@ -9924,7 +10071,7 @@ snapshots: '@jest/types': 27.5.1 chalk: 4.1.2 cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.2 + collect-v8-coverage: 1.0.3 execa: 5.1.1 glob: 7.2.3 graceful-fs: 4.2.11 @@ -9942,16 +10089,16 @@ snapshots: jest-serializer@27.5.1: dependencies: - '@types/node': 20.19.18 + '@types/node': 20.19.37 graceful-fs: 4.2.11 jest-snapshot@27.5.1: dependencies: '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.4) + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/babel__traverse': 7.28.0 @@ -9968,14 +10115,14 @@ snapshots: jest-util: 27.5.1 natural-compare: 1.4.0 pretty-format: 27.5.1 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color jest-util@27.5.1: dependencies: '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -9994,7 +10141,7 @@ snapshots: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 20.19.18 + '@types/node': 20.19.37 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -10002,7 +10149,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.18 + '@types/node': 20.19.37 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -10039,7 +10186,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@3.14.1: + js-yaml@3.14.2: dependencies: argparse: 1.0.10 esprima: 4.0.1 @@ -10057,7 +10204,7 @@ snapshots: jsdom@16.7.0: dependencies: abab: 2.0.6 - acorn: 8.15.0 + acorn: 8.16.0 acorn-globals: 6.0.0 cssom: 0.4.4 cssstyle: 2.3.0 @@ -10070,7 +10217,7 @@ snapshots: http-proxy-agent: 4.0.1 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.22 + nwsapi: 2.2.23 parse5: 6.0.1 saxes: 5.0.1 symbol-tree: 3.2.4 @@ -10188,6 +10335,8 @@ snapshots: lodash@4.17.21: {} + lodash@4.17.23: {} + longest-streak@3.1.0: {} lru-cache@5.1.1: @@ -10200,7 +10349,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.4 makeerror@1.0.12: dependencies: @@ -10560,6 +10709,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@8.0.4: dependencies: brace-expansion: 2.0.2 @@ -10632,7 +10785,7 @@ snapshots: dependencies: boolbase: 1.0.0 - nwsapi@2.2.22: {} + nwsapi@2.2.23: {} nypm@0.5.4: dependencies: @@ -10741,7 +10894,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -11045,6 +11198,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -11100,7 +11259,7 @@ snapshots: safer-buffer@2.1.2: {} - sard-uniapp@1.24.7(@dcloudio/types@3.4.21)(vue@3.5.22(typescript@5.8.3)): + sard-uniapp@1.27.1(@dcloudio/types@3.4.21)(vue@3.5.22(typescript@5.8.3)): dependencies: '@dcloudio/types': 3.4.21 vue: 3.5.22(typescript@5.8.3) @@ -11129,6 +11288,8 @@ snapshots: semver@7.7.2: {} + semver@7.7.4: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -11322,7 +11483,7 @@ snapshots: dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 3.1.5 throat@6.0.2: {} @@ -11650,7 +11811,7 @@ snapshots: whatwg-url@8.7.0: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 tr46: 2.1.0 webidl-conversions: 6.1.0 diff --git a/code/container-ocr/ocr-uniapp/src/App.vue b/code/container-ocr/ocr-uniapp/src/App.vue index 18762e96..5d6d11c7 100644 --- a/code/container-ocr/ocr-uniapp/src/App.vue +++ b/code/container-ocr/ocr-uniapp/src/App.vue @@ -6,7 +6,7 @@ import { useUserStore } from './store' const userStore = useUserStore() onLaunch(() => { - // userStore.requireLogin() + userStore.requireLogin() }) onMounted(() => { @@ -18,7 +18,7 @@ onUnmounted(() => { diff --git a/code/container-ocr/ocr-uniapp/src/pages-sub/login/index.vue b/code/container-ocr/ocr-uniapp/src/pages-sub/login/index.vue index 99601b70..d80a6c0b 100644 --- a/code/container-ocr/ocr-uniapp/src/pages-sub/login/index.vue +++ b/code/container-ocr/ocr-uniapp/src/pages-sub/login/index.vue @@ -2,7 +2,7 @@ import type { FieldValidateError, FormExpose, FormRules } from 'sard-uniapp' import { getWindowInfo, toast } from 'sard-uniapp' import { reactive, ref } from 'vue' -import { loaddataApi, loginApi } from '@/request/api' +import { loginApi } from '@/request/api' import { useUserStore } from '@/store' const userStore = useUserStore() @@ -12,7 +12,7 @@ const startOffset = ref(getWindowInfo().statusBarHeight + 60) const _loading = ref(false) const formModel = reactive({ - orgid: userStore.userInfo.orgid, + orgid: 'g3hd', userid: userStore.userInfo.userid, password: '', }) @@ -42,47 +42,50 @@ async function onSubmit() { password: formModel.password, }) - if (!res?.data) { - toast.fail('登录失败,请检查账号密码') - return - } - - userStore.userInfo = { - ...userStore.userInfo, - orgid: formModel.orgid, - userid: formModel.userid, - token: res.data, - } - - let username = '' - try { - const userRes = await loaddataApi({ - viewname: 'v_b_truck', - soption: '', - orderfield: '', - searchcondition: `b_id = '${formModel.userid}'`, - }) - if (userRes?.data?.length > 0) { - username = userRes.data[0].v_worker_name || '' + if (res.success) { + userStore.userInfo = { + ...userStore.userInfo, + orgid: formModel.orgid, + userid: formModel.userid, + token: res.data.token, + username: res.data.username, } - } - catch (err) { - console.warn('获取用户信息失败', err) + + uni.showToast({ + title: '登录成功', + icon: 'success', + }) + + uni.switchTab({ + url: '/pages/home/index', + }) } - userStore.userInfo = { - ...userStore.userInfo, - username, - } + // if (!res?.data) { + // toast.fail('登录失败,请检查账号密码') + // return + // } - uni.showToast({ - title: '登录成功', - icon: 'success', - }) + // userStore.userInfo = { + // ...userStore.userInfo, + // orgid: formModel.orgid, + // userid: formModel.userid, + // token: res.data, + // } - uni.switchTab({ - url: '/pages/home/index', - }) + // userStore.userInfo = { + // ...userStore.userInfo, + // username, + // } + + // uni.showToast({ + // title: '登录成功', + // icon: 'success', + // }) + + // uni.switchTab({ + // url: '/pages/home/index', + // }) } catch (err: any) { // 表单验证错误 @@ -91,7 +94,6 @@ async function onSubmit() { } else { console.error(err) - toast.fail(err.message || '登录异常') } } finally { @@ -122,7 +124,7 @@ async function onSubmit() { - + 账号 diff --git a/code/container-ocr/ocr-uniapp/src/pages-sub/upload/index.vue b/code/container-ocr/ocr-uniapp/src/pages-sub/upload/index.vue index 2eb1cf66..05b83140 100644 --- a/code/container-ocr/ocr-uniapp/src/pages-sub/upload/index.vue +++ b/code/container-ocr/ocr-uniapp/src/pages-sub/upload/index.vue @@ -1,49 +1,322 @@ diff --git a/code/container-ocr/ocr-uniapp/src/pages.json b/code/container-ocr/ocr-uniapp/src/pages.json index d0ee7cd8..955e30b3 100644 --- a/code/container-ocr/ocr-uniapp/src/pages.json +++ b/code/container-ocr/ocr-uniapp/src/pages.json @@ -2,7 +2,7 @@ "globalStyle": { "navigationStyle": "default", "navigationBarTitleText": "App", - "navigationBarBackgroundColor": "#fff", + "navigationBarBackgroundColor": "#f8f8f8", "navigationBarTextStyle": "black", "backgroundColor": "#FFFFFF" }, diff --git a/code/container-ocr/ocr-uniapp/src/pages/account/index.vue b/code/container-ocr/ocr-uniapp/src/pages/account/index.vue index cd9037a0..cb60e663 100644 --- a/code/container-ocr/ocr-uniapp/src/pages/account/index.vue +++ b/code/container-ocr/ocr-uniapp/src/pages/account/index.vue @@ -5,7 +5,7 @@ import { useUserStore } from '@/store' const userStore = useUserStore() onShow(() => { - // userStore.requireLogin() + userStore.requireLogin() }) function handleLoginOut() { @@ -94,7 +94,11 @@ const avatarText = computed(() => { + - - - - -